Перевантаження за типом повернення


80

Я прочитав тут кілька запитань на тему SO щодо цієї теми, яка мені здається поки що незрозумілою. Я тільки почав вивчати C ++, і ще не вивчав шаблони чи перевантаження оператора тощо.

Зараз є простий спосіб перевантажити

class My {
public:
    int get(int);
    char get(int);
}

без шаблонів чи дивної поведінки? чи я повинен просто

class My {
public:
    int get_int(int);
    char get_char(int);
}

?



1
@AdamV, мені дуже подобається ваш коментар. Короткий, але цілком твердий.
Pouya

@Adam V Насправді вже існує така неоднозначність з прийняттям адреси перевантаженої функції. У такому випадку має бути якесь очікування виразу. Якщо такого немає, програма неправильно сформована. І це вже реалізовано. Я не думаю, що буде дуже важко використовувати однакові правила для реалізації перевантаження функції за типом повернення. Отже, у вашому конкретному прикладі двозначність буде усунута за допомогою приведення повернутого типу. Екземпляр із intповернутим значенням буде виглядати так (int)get(9)і з charтаким (char)get(9).
AnArrayOfFunctions

Коли ви доберетесь сюди, я вважаю, що найкращим варіантом буде думати про дві різні назви функцій, як запропоновано Лучіаном.
Кемін Чжоу,

Відповіді:


100

Ні, немає. Ви не можете перевантажити методи на основі типу повернення.

Дозвіл на перевантаження враховує підпис функції . Підпис функції складається з:

  • назва функції
  • cv-кваліфікація
  • типи параметрів

І ось цитата:

1.3.11 підпис

інформація про функцію, яка бере участь у вирішенні перевантажень (13.3): її перелік типів параметрів (8.3.5) і, якщо функція є членом класу, cv-кваліфікатори (якщо такі є) щодо самої функції та класу в якому оголошена функція-член. [...]

Варіанти:

1) змініть назву методу:

class My {
public:
    int getInt(int);
    char getChar(int);
};

2) параметр out:

class My {
public:
    void get(int, int&);
    void get(int, char&);
}

3) шаблони ... в цьому випадку надмірні.


17
Ви не можете перевантажити звичайну функцію за типом повернення, але компілятор буде вибирати між операторами перетворення на основі отриманого типу; Ви можете скористатися цим, щоб створити проксі-сервер, який ефективно діє так, ніби ви перевантажили тип повернення.
Джеймс Канце,

2
@JeffPigarelli Шаблони членів кошти шаблон рішення: My::get<T>(int). Це допустима альтернатива _якщо 1) вам доведеться обробляти багато різних типів, усі з однаковим базовим кодом (наприклад boost::lexical_cast<T>( someStringValue ), або ви повинні мати можливість викликати ці функції з якогось іншого шаблону ( myMy.get<T>( i )де Tаргумент цього іншого шаблону В іншому випадку, як каже Лучіан, вони переборюють.
Джеймс Канце,

39
Зверніть увагу, що причиною того, що ви не можете перевантажити за типом повернення, є те, що C ++ дозволяє вам відкинути значення виклику функції. Отже, якщо ви просто зателефонували my.get(0);компілятору, у вас не було б можливості вирішити, який фрагмент коду виконувати.
benzado 05.03.12

9
@benzado в цьому випадку, він повинен кинути компілятор помилки тільки в таких випадках, в іншому випадку він повинен вивести тип , як це робить в настільки багатьох інших сценаріях вже.
rr-

2
@benzado з тих же міркувань void foo(int x = 0) {} void foo(double x = 0) {}слід заборонити. Однак це не так. Тільки у випадку, коли компілятор справді не може розрізнити ( foo()), ви отримаєте помилку
greatest_prime_is_463035818

82

Це можливо, але я не впевнений, що це техніка, яку я рекомендую початківцям. Як і в інших випадках, коли ви хочете, щоб вибір функцій залежав від того, як використовується значення, що повертається, ви використовуєте проксі-сервер; спочатку визначте такі функції, як getCharі getInt, а потім загальну, get()яка повертає такий проксі:

class Proxy
{
    My const* myOwner;
public:
    Proxy( My const* owner ) : myOwner( owner ) {}
    operator int() const
    {
        return myOwner->getInt();
    }
    operator char() const
    {
        return myOwner->getChar();
    }
};

Поширте його на стільки типів, скільки вам потрібно.


10
+1, хоча кутовий випадок, оператор перетворення фактично перевантажений на тип повернення, і його можна використовувати, щоб отримати цю функцію де завгодно.
Matthieu M.

3
@MatthieuM. Приблизно скрізь, але зі звичайними застереженнями щодо неявних перетворень. Ви ризикуєте внести неясності, яких інакше не було б. Однак у випадку з проксі, я думаю, що ризик незначний - у вас не буде екземплярів типу проксі, крім випадку, коли ви хочете неявного перетворення. Також зауважте, що перетворення в проксі-сервері вважається одним перетворенням, визначеним користувачем. Якщо вам потрібно std::string, а проксі-сервер пропонує лише operator char const*(), це не спрацює.
Джеймс Канце,

навіщо тут використовувати проксі, я не можу згадати жодного випадку, коли проксі є обов’язковим. Чи можете ви надати його? Дякую!
陳 力

8

Ні, ви не можете перевантажити за типом повернення; лише за типами параметрів, а також за класифікаторами const / volatile.

Однією з альтернатив може бути "повернення" за допомогою посилального аргументу:

void get(int, int&);
void get(int, char&);

хоча я, мабуть, використовував би шаблон, або інакше названі функції, як ваш другий приклад.


API EFI, де тип повернення - це intкод помилки.
Коул Джонсон,

1
Будьте обережні, типи char та int можуть неявно перетворюватися.
Kemin Zhou

5

Ви можете думати так:

Ти маєш:

  int get(int);
  char get(int);

І не обов’язково збирати повернене значення функції під час виклику.

Тепер Ви закликаєте

  get(10);  -> there is an ambiguity here which function to invoke. 

Отже, немає значення, якщо дозволено перевантаження на основі типу повернення.


4

Відроджуючи старий потік, але я бачу, що ніхто не згадував про перевантаження реф-кваліфікаторами. Ref-кваліфікатори - це мовна функція, додана в C ++ 11, і я нещодавно натрапив на неї - вона не настільки поширена, як, наприклад, cv-кваліфікатори. Основна ідея полягає у розрізненні двох випадків: коли функція-член викликається на об'єкт rvalue і коли викликається на об'єкт lvalue. В основному ви можете написати щось подібне (я трохи модифікую код OP):

#include <stdio.h>

class My {
public:
    int get(int) & { // notice &
        printf("returning int..\n");
        return 42;
    }
    char get(int) && { // notice &&
        printf("returning char..\n");
        return 'x';
    };
};

int main() {
    My oh_my;
    oh_my.get(13); // 'oh_my' is an lvalue
    My().get(13); // 'My()' is a temporary, i.e. an rvalue
}

Цей код видасть такий результат:

returning int..
returning char..

Звичайно, як у випадку з cv-кваліфікаторами, обидві функції могли повернути однаковий тип, і перевантаження все одно було б успішним.


4

Як зазначалося раніше, шаблони в цьому випадку є надмірними, але це все ще варіант, про який варто згадати.

class My {
public:
    template<typename T> T get(int);
};

template<> int My::get<int>(int);
template<> char My::get<char>(int);

3

Хоча більшість інших коментарів щодо цієї проблеми є технічно правильними, ви можете ефективно перевантажити повернене значення, якщо поєднати його з перевантажуючим вхідним параметром. Наприклад:

class My {
public:
    int  get(int);
    char get(unsigned int);
};

ДЕМО:

#include <stdio.h>

class My {
public:
    int  get(         int x) { return 'I';  };
    char get(unsinged int x) { return 'C';  };
};

int main() {

    int i;
    My test;

    printf( "%c\n", test.get(               i) );
    printf( "%c\n", test.get((unsigned int) i) );
}

Результатом цього є:

I 
C

6
що повністю змінює підпис функції, щоб ви не перевантажували за типом повернення, ви просто перевантажували
Дадо

Я успішно використав цей метод для повернення різноманітних значень для API JSON C ++, що працює. Працювали чудово! Хоча технічно не перевантажується за типом повернення, він виконує наміри різних типів повернення з однаковим іменем функції, є дійсним і зрозумілим C ++ і має незначні накладні витрати (одна інстанція змінної у виклику функції).
guideotex

Хоча це не перевантаження типом повернення, воно робить свою роботу. змушує мене сказати "підлий"
Маркус,

2

У C ++ немає можливості перевантажити типом повернення. Без використання шаблонів, використання get_intі get_charбуде найкращим з усіх можливих результатів.


Тільки щоб бути впевненим: щось на зразок template <class T> T get(int)буде працювати?
Ніклас Б.

4
Так, @Niklas, але вам доведеться називати це як get<int>або get<char>, що насправді вас не надто приваблює, get_intі get_charякщо ви також не використовуєте інші функції шаблону.
Роб Кеннеді

@Rob: Ну, компілятор може визначити, Tчи є у вас щось подібне T get(T). Якщо ви телефонуєте get('a'), компілятор Tвизначає, що це, charі вам не потрібно явно дзвонити get<char>('a'). Я все ще не впевнений, що це стандарт, хоча я думаю, що це так. FYI, як GCC, так і Clang підтримують це.
неткодер

1
Це цілком стандартно, @Netcoder, але це не випадок, коли компілятор виводить лише той тип повернення, який, на вашу думку, був можливим. У вашому прикладі компілятор виводить тип аргументу, і як тільки він це знає, він заповнює значення Tскрізь деінде, включаючи тип повернення. Я очікував, що ви наведете приклад виведення компілятора Tдля функції у першому коментарі Нікласа.
Роб Кеннеді

2

Ви не можете перевантажити методи на основі типів повернення. Найкраще - створити дві функції з дещо іншим синтаксисом, наприклад, у другому фрагменті коду.


0

ви не можете перевантажити функцію на основі типу повернення функції. Ви можете накладати на основу тип і кількість аргументів, які приймає ця функція.


0

Я використав відповідь Джеймса Канзе, використовуючи проксі:

https://stackoverflow.com/a/9569120/262458

Я хотів уникати використання потворних static_casts у порожнечі *, тому зробив це:

#include <SDL_joystick.h>
#include <SDL_gamecontroller.h>

struct JoyDev {
    private:
        union {
            SDL_GameController* dev_gc = nullptr;
            SDL_Joystick*       dev_js;
        };
    public:
        operator SDL_GameController*&() { return dev_gc; }
        operator SDL_Joystick*&()       { return dev_js; }

        SDL_GameController*& operator=(SDL_GameController* p) { dev_gc = p; return dev_gc; }
        SDL_Joystick*&       operator=(SDL_Joystick* p)       { dev_js = p; return dev_js; }
};

struct JoyState {
    public:
        JoyDev dev;
};

int main(int argc, char** argv)
{
    JoyState js;

    js.dev = SDL_JoystickOpen(0);

    js.dev = SDL_GameControllerOpen(0);

    SDL_GameControllerRumble(js.dev, 0xFFFF, 0xFFFF, 300);

    return 0;
}

Працює ідеально!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.