Jak zwrócić poprawny typ danych w szablonach?


9
#include <iostream>
using namespace std;

template <class X, class Y>
Y big(X a, Y b)
{
   if (a > b)
      return (a);
   else return (b);
}

int main()
{
   cout << big(32.8, 9);
}

Tutaj używam szablonów w CPP, więc kiedy bigwywołuję funkcję z pominięciem argumentów doublei inttypu, chcę uzyskać odpowiedź zwrotną, która jest double. Wpisz tutaj, zwraca 32zamiast 32.8.

Jak uzyskać pożądaną wydajność? Jak napisać odpowiedni typ zwracanej bigfunkcji?


1
Funkcja może zwrócić tylko jeden ustalony typ. W czasie wykonywania nie można wybrać typu, który ma zostać zwrócony.
Jesper Juhl

1
Możesz przyjrzeć się, w jaki sposób std::maxjest wdrażany. Zwracany typ funkcji musi być znany w czasie kompilacji w C ++. Dlatego nie można mieć tego typu zwrotu zależnego od wartości środowiska wykonawczego parametrów. Dlatego dla takiej funkcji potrzebne są oba parametry, aby mieć ten sam typ (tj. Mieć typ X, ale nie Y).
Boris Dalstein

Odpowiedzi:


12

Funkcja może mieć tylko jeden typ zwracany, który musi być znany w czasie kompilacji. Można jednak użyć std::common_type, aby zwrócić typ, dla którego oba parametry mogą być konwertowane implicite.

To by było

#include <type_traits>
template <class X, class Y>
typename std::common_type<X,Y>::type big(X a, Y b)
{
   if (a > b)
      return a;
   else return b;
}

I aby sprawdzić, czy faktycznie zwraca a doublepo przejściu inti a doublemożemy zrobić:

int main() {
    auto x = big(4.2,42);
    std::cout << std::is_same<decltype(x),double>::value;
}

Które wydruki

1

PS: std::common_typemoże korzystać z trójskładnikowego operatora za sceną i jako takie rozwiązanie nie różni się niczym od innych odpowiedzi ( auto+ trójskładnikowe). Prawdziwą siłą std::common_typejest to, że akceptuje dowolną liczbę parametrów.


10

Typ zwracany musi zostać określony w czasie kompilacji. Możesz użyć końcowego powrotu z operatorem warunkowym , jeśli jesteś ograniczony do .

template <typename X, typename Y>
auto big(X&& a, Y&& b) -> decltype(a > b ? a : b) // ---> like this
{
   return  a > b ? a : b;
}

Zobacz na żywo


Jeśli jednak masz dostęp do lub wyższej, autowystarczy, że kompilator wydedukuje odpowiedni typ, jeśli użyjesz go razem z operatorem warunkowym w następujący sposób:

template <typename X, typename Y>
auto big(X a, Y b)
{
   return  a > b ? a : b;
}

Zobacz na żywo


Końcowy typ zwrotu nie jest potrzebny, przynajmniej od C ++ 14.
sweenish

@walnut Dobry punkt. jeszcze jedną opcją jest odniesienie do przekazywania?
JeJo

1
@JeJo Tak, przypuszczam, że to również jest w porządku, ale prawdopodobnie bezcelowe, ponieważ nie modyfikujesz żadnego argumentu, a typ zwracany będzie nadal referencją do wartości w obu przypadkach (choć potencjalnie nie const).
orzech

Usunąłem swoje komentarze, ponieważ nie mają one już zastosowania, ale sugeruję dodanie ostrzeżenia do odpowiedzi, że nie można przyjąć parametrów według wartości.
orzech

Jeśli ktoś spojrzy na twój kod, wygląda na to, że przekazany parametr zadecyduje, jaki typ zwrotu ktoś otrzyma, co nie jest prawdą! Zawsze otrzymasz podwójne, nawet jeśli a jest większe niż b.
Klaus

4

Oznaczając typ zwrotu jako Yi przekazując intjako drugi parametr, wyraźnie wskazałeś, że Yjest to int. Nie ma tu niespodzianek.

#include <iostream>

template <typename X, typename Y>
decltype(auto) big(const X& a, const Y& b)  // return type can just be auto as well 
{
    return a > b ? a : b;
}

int main()
{
    std::cout << big(32.8, 9) << '\n';
    std::cout << big(9, 32.8) << '\n';
    std::cout << big(32.8, 90) << '\n';
    std::cout << big(90, 32.8) << '\n';
}

Spowoduje to wydrukowanie wszystkich czterech poprawnych wartości na ekranie.

https://godbolt.org/z/fyGsmo

Jedną rzeczą, na którą należy zwrócić uwagę, jest to, że będzie to działać tylko dla typów, które można porównywać ze sobą, tj. Kompilator niejawnie przekonwertuje jeden typ na drugi dla porównania.

WAŻNE : parametry należy traktować jako odniesienie, aby uniknąć nieokreślonego zachowania. Ma to związek z typem zwrotu, którego uparcie się trzymam. decltype(auto)może zwracać odniesienia do typów. Jeśli zwrócisz coś lokalnego do funkcji (liczą się argumenty), otrzymasz niezdefiniowane zachowanie.


@walnut Przypadkowe zwrócenie referencji jest o wiele trudniejsze niż na tej stronie. Ale dobrze wiedzieć o nieokreślonym zachowaniu. Zresztą i tak nie napisałbym kodu; to odpowiedź na pytanie.
sweenish

1
Ach Przeczytałem twój wcześniejszy komentarz jako dwa odrębne punkty, a nie skutek i przyczynę. Mogę dokonać odpowiedniej edycji.
sweenish

Dodałem dodatkowe zastrzeżenie.
sweenish

2

Prawdopodobnie nie jest to właściwe rozwiązanie dla Twojej precyzyjnej sytuacji - inne odpowiedzi prawdopodobnie będą znacznie bliżej tego, czego chcesz.

Jeśli jednak z jakiegoś powodu naprawdę musisz zwracać całkowicie różne typy w środowisku wykonawczym, poprawnym rozwiązaniem (od wersji ) jest użycie std::variantunii, która jest rodzajem unii bezpiecznej dla typu.

#include <variant>

template <typename X, typename Y>
std::variant<X, Y> max(X a, Y b) {
  if (a > b)
    return std::variant<X, Y>(std::in_place_index_t<0>, a);
  else
    return std::variant<X, Y>(std::in_place_index_t<1>, b);
}

Zauważ, że wtedy na dzwoniącym spoczywa ciężar radzenia sobie ze zwróconą wartością, najprawdopodobniej za pomocą std::visititp.


-2

Zwraca int, ponieważ Y jest int i rzuca na niego 32.8. Gdy nazwiesz dużą, 32,82 jest liczbą zmiennoprzecinkową, ale 8 jest liczbą całkowitą, a typem funkcji jest Y, która również jest liczbą całkowitą.

Naprawdę nie możesz tego naprawić, ponieważ musisz wiedzieć w czasie wykonywania, które typy dużych zwrotów, więc ułóż aib typu tego samego typu:

    #include <iostream>
    using namespace std;

    template <typename X>

    X big (X a, X b)
    {
    if (a>b)
    return a;

    else return b;
    }

    int main()
    {
    cout<< big (32.8, 9.0);
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.