Przeciążanie przez zwracany typ

81

Przeczytałem tutaj kilka pytań dotyczących SO na ten temat, który wydaje mi się jeszcze niejasny. Dopiero co zacząłem uczyć się C ++ i nie studiowałem jeszcze szablonów, przeciążenia operatorów i tym podobnych.

Czy istnieje prosty sposób na przeciążenie

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

bez szablonów lub dziwnego zachowania? czy powinienem po prostu

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

?

But
źródło
1
@AdamV, bardzo mi się podoba twój komentarz. Krótkie, ale całkowicie solidne.
Pouya
@Adam V Właściwie już jest taka niejednoznaczność z pobieraniem adresu przeciążonej funkcji. W takim przypadku powinno być jakieś oczekiwanie na wyrażenie. Jeśli takiego nie ma, program jest źle sformułowany. I to już zostało wdrożone. Nie sądzę, że będzie bardzo trudno zastosować te same zasady do implementowania przeciążania funkcji według typu zwracanego. W twoim konkretnym przykładzie niejednoznaczność zostanie usunięta z rzutem zwracanego typu. Instancja z intwartością zwracaną będzie wyglądać tak (int)get(9)i chartak (char)get(9).
AnArrayOfFunctions
Kiedy dotrzesz tutaj, myślę, że najlepszym wyborem będzie myślenie o dwóch różnych nazwach funkcji, takich jak sugerowane przez Luchiana.
Kemin Zhou

Odpowiedzi:

101

Nie, nie ma. Nie można przeciążać metod opartych na zwracanym typie.

Rozwiązanie przeciążenia uwzględnia sygnaturę funkcji . Podpis funkcji składa się z:

  • nazwa funkcji
  • CV-kwalifikatory
  • typy parametrów

A oto cytat:

1.3.11 podpis

informacje o funkcji, która uczestniczy w rozwiązywaniu przeciążenia (13.3): jej lista typów parametrów (8.3.5) i, jeśli funkcja jest składową klasy, kwalifikatory cv (jeśli istnieją) na samej funkcji i klasie w którym zadeklarowano funkcję składową. […]

Opcje:

1) zmień nazwę metody:

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

2) parametr out:

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

3) szablony ... w tym przypadku przesada.

Luchian Grigore
źródło
17
Nie można przeciążać normalnej funkcji zwracanym typem, ale kompilator wybierze między operatorami konwersji na podstawie typu wynikowego; możesz to wykorzystać, aby utworzyć proxy, które skutecznie działa tak, jakbyś był przeciążony typem powrotu.
James Kanze,
2
@JeffPigarelli środków rozwiązanie szablon szablony członkowskie: My::get<T>(int). Jest to poprawna alternatywa _jeśli 1) musisz obsługiwać wiele różnych typów, wszystkie z tym samym podstawowym kodem (np. boost::lexical_cast<T>( someStringValue )Lub musisz mieć możliwość wywołania tych funkcji z innego szablonu ( myMy.get<T>( i )gdzie Tjest argumentem tego innego szablonu W przeciwnym razie, jak mówi Luchian, są przesadą
James Kanze
39
Zauważ, że powodem , dla którego nie możesz przeciążać na podstawie zwracanego typu, jest to, że C ++ pozwala odrzucić wartość wywołania funkcji. Więc jeśli po prostu zadzwonisz my.get(0);do kompilatora, nie będziesz miał możliwości zdecydowania, który fragment kodu wykonać.
benzado
9
@benzado w tym przypadku powinny one rzucać kompilatora błędy tylko w takich przypadkach, w przeciwnym razie należy wywnioskować typ jak to ma miejsce w tak wielu innych scenariuszy już.
rr-
2
@benzado z tego samego powodu void foo(int x = 0) {} void foo(double x = 0) {}powinno być niedozwolone. Tak jednak nie jest. Tylko w przypadku, gdy kompilator naprawdę nie może odróżnić ( foo()), pojawi się błąd
large_prime_is_463035818
82

Jest to możliwe, ale nie jestem pewien, czy jest to technika, którą poleciłbym początkującym. Podobnie jak w innych przypadkach, gdy chcesz, aby wybór funkcji zależał od sposobu wykorzystania wartości zwracanej, używasz serwera proxy; najpierw zdefiniuj funkcje, takie jak getChari getInt, a następnie rodzaj, get()który zwraca proxy w następujący sposób:

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

Rozszerz go na tyle typów, ile potrzebujesz.

James Kanze
źródło
10
+1, chociaż jest to przypadek narożny, operator konwersji jest w rzeczywistości przeciążony typem zwrotnym i można go wykorzystać, aby uzyskać tę funkcję wszędzie.
Matthieu M.
3
@MatthieuM. Prawie wszędzie, ale ze zwykłymi zastrzeżeniami dotyczącymi niejawnych konwersji. Ryzykujesz wprowadzeniem niejasności, których inaczej by tam nie było. Jednak w przypadku proxy myślę, że ryzyko jest niewielkie - nie będziesz mieć instancji typu proxy innych niż w przypadku, gdy chcesz niejawnej konwersji. Należy również zauważyć, że konwersja w proxy liczy się jako jedna konwersja zdefiniowana przez użytkownika. Jeśli potrzebujesz std::string, a proxy oferuje tylko operator char const*(), to nie zadziała.
James Kanze
po co tutaj używać proxy, nie przychodzą mi do głowy żadne przypadki, w których proxy jest koniecznością. Czy możesz podać jeden? dzięki!
陳 力
8

Nie, nie możesz przeciążać zwracanego typu; tylko według typów parametrów i kwalifikatorów const / volatile.

Jedną z możliwości byłoby „zwrócenie” przy użyciu argumentu odwołania:

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

chociaż prawdopodobnie użyłbym szablonu lub funkcji o innych nazwach, takich jak twój drugi przykład.

Mike Seymour
źródło
a la EFI API, gdzie typem zwracanym jest intkod błędu.
Cole Johnson
1
Uważaj, typy char i int mogą być niejawnie konwertowane.
Kemin Zhou
5

Możesz myśleć w ten sposób:

Ty masz:

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

Nie jest też obowiązkowe zbieranie wartości zwracanej przez funkcję podczas wywoływania.

Teraz wzywasz

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

Tak więc nie ma znaczenia, jeśli przeciążanie jest dozwolone na podstawie zwracanego typu.

Kim jestem
źródło
4

Wskrzeszenie starego wątku, ale widzę, że nikt nie wspomniał o przeciążaniu przez ref-kwalifikatory. Kwalifikatory ref to funkcja języka dodana w C ++ 11 i dopiero niedawno się na nią natknąłem - nie jest tak rozpowszechniona jak np. Kwalifikatory cv. Główną ideą jest rozróżnienie między dwoma przypadkami: kiedy funkcja składowa jest wywoływana na obiekcie rvalue i kiedy jest wywoływana na obiekcie lvalue. Możesz w zasadzie napisać coś takiego (nieznacznie modyfikuję kod 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
}

Ten kod wygeneruje następujące dane wyjściowe:

returning int..
returning char..

Oczywiście, tak jak w przypadku kwalifikatorów cv, obie funkcje mogłyby zwrócić ten sam typ, a przeciążanie nadal by się powiodło.

Miljen Mikic
źródło
4

Jak wspomniano wcześniej, szablony są w tym przypadku przesadą, ale nadal jest to opcja, o której warto wspomnieć.

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

template<> int My::get<int>(int);
template<> char My::get<char>(int);
fabda01
źródło
3

Chociaż większość innych komentarzy dotyczących tego problemu jest poprawna technicznie, możesz skutecznie przeciążać zwracaną wartość, jeśli połączysz ją z przeciążeniem parametru wejściowego. Na przykład:

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

PRÓBNY:

#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) );
}

Wynikiem tego jest:

I 
C
codechimp
źródło
6
to całkowicie zmienia sygnaturę funkcji, więc nie przeciążasz zwracanego typu, po prostu przeładowujesz
Dado
Z powodzeniem użyłem tej metody, aby zwrócić różne wartości dla uruchomionego w środowisku produkcyjnym interfejsu API C ++ JSON API. Działało świetnie! Chociaż technicznie nie jest przeciążony przez typ zwracany, realizuje intencję różnych typów zwracanych o tej samej nazwie funkcji, jest prawidłowy i przejrzysty w C ++ oraz ma niewielki narzut (tworzenie pojedynczej zmiennej w wywołaniu funkcji).
guidotex,
Chociaż nie jest to przeciążenie typem zwracanym, spełnia swoje zadanie. sprawia, że ​​mówię „podstępne”
Marcus,
2

Nie ma możliwości przeciążenia przez zwracany typ w C ++. Bez użycia szablonów, za pomocą get_inti get_charbędzie to najlepsze co możesz zrobić.

sepp2k
źródło
Dla pewności: coś takiego template <class T> T get(int)mogłoby zadziałać?
Niklas B.
4
Tak, @Niklas, ale musiałbyś nazywać to jako get<int>lub get<char>, co tak naprawdę nie daje ci wiele przewagi get_inti get_charjeśli nie używasz również innych funkcji szablonu.
Rob Kennedy
@Rob: Cóż, kompilator może określić, Tczy masz coś takiegoT get(T) . Jeśli wywołasz get('a'), kompilator wywnioskuje, że Tjest to a chari nie musisz jawnie wywoływać get<char>('a'). Nadal nie jestem pewien, czy to standard, chociaż myślę, że tak. FYI, zarówno GCC, jak i Clang to obsługują.
netcoder
1
To całkowicie standardowe, @Netcoder, ale nie jest to przypadek, w którym kompilator wydedukuje tylko typ zwrotu, który sugerowałeś, że jest możliwy. W naszym przykładzie kompilator wyprowadza typ argumentu, a gdy już to wie, wypełnia wartość Twszystkich innych elementów, łącznie z typem zwracanym. Spodziewałem się, że podasz przykład kompilatora wnioskującego Tdla funkcji w pierwszym komentarzu Niklasa.
Rob Kennedy
2

Nie można przeciążać metod opartych na typach zwracanych. Najlepszym rozwiązaniem jest utworzenie dwóch funkcji o nieco innej składni, na przykład w drugim fragmencie kodu.

Frecklefoot
źródło
0

nie możesz przeciążać funkcji w oparciu o zwracany typ funkcji. możesz przesadzić na podstawie typu i liczby argumentów, które przyjmuje ta funkcja.

AlexDan
źródło
0

Użyłem odpowiedzi Jamesa Kanze za pomocą proxy:

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

Chciałem uniknąć używania wielu brzydkich static_castów na void *, więc zrobiłem to:

#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;
}

Działa świetnie!

Rafael Kitover
źródło