typ zwracany std :: pair <auto, auto>

16

Grałem około z autow std::pair. W poniższym kodzie funkcja fpowinna zwracać std::pairtypy zależne od parametru szablonu.

Przykład roboczy:

PRZYKŁAD 1

template <unsigned S>
auto f()
{
    if constexpr (S == 1)
        return std::pair{1, 2}; // pair of ints
    else if constexpr (S == 2)
        return std::pair{1.0, 2.0}; // pair of doubles
    else
        return std::pair{0.0f, 0.0f}; // pair of floats
}

Działa to z gcc 9.2, gcc 10.0, clang 9.0 i clang 10.0.

Następnie chciałem jawnie napisać typ zwracany std::pairze względów przejrzystości:

PRZYKŁAD 2

template <unsigned S>
std::pair<auto, auto> f()
{
    if constexpr (S == 1)
        return {1, 2};
    /* ... */
}

Zarówno gcc 9.2 / 10.0, jak i clang 9.0 / 10.0 nie skompilowały tego.

gcc 9.2

error: invalid use of 'auto'
error: template argument 1 is invalid // first argument (auto) of std::pair
error: template argument 2 is invalid // second argument (auto) of std::pair
error: cannot convert '<brace-enclosed initializer list>' to 'int' in return

Po ostatnim komunikacie o błędzie gcc 9.2 wydaje się wierzyć, że std::pair<auto, auto>jest to int. Jak można to wyjaśnić?

gcc 10.0

error: returning initializer list

Ten błąd jest zrozumiały, ale spodziewałem się, że std::pairzostanie wywołany konstruktor, czy też czegoś mi brakuje?

klang 9.0 i 10.0

'auto' not allowed in template argument
excess elements in scalar initializer
no matching function for call to 'f'

Ok, clangowi się to nie podoba. Na podstawie drugiego komunikatu o błędzie wydaje się, że clang uważa również, że typem zwrotu jest int.

Wreszcie, aby naprawić błąd uzyskany podczas kompilacji z gcc 10.0, postanowiłem zwrócić std::pairjawnie:

PRZYKŁAD 3

template <unsigned S>
std::pair<auto, auto> f()
{
    if constexpr (S == 1)
        return std::pair{1, 2};
    /* ... */
}

klang 9.0 i 10.0

Tak jak poprzednio, ale z dodatkowym:

no viable conversion from returned value of type 'std::pair<int, int>' to function return type 'int'

Tutaj klang nadal myśli, że wracamy int?

gcc 9.2

Tak samo jak ostatnio.

gcc 10.0

To działa!

Wydaje mi się, że niektóre funkcje wciąż muszą zostać zaimplementowane lub w jednej z opisanych powyżej sytuacji, czy istnieje kompilator, który jest poprawny, a drugi źle? Moim zdaniem przykład 2 powinien działać. A może nie?

mfnx
źródło

Odpowiedzi:

23

Składnia:

std::pair<auto, auto> f() { return std::pair(1, 2); }
~~~~~~~~~~~~~~~~~~~~~

Był częścią oryginalnej Koncepcji TS, ale nie został uwzględniony w propozycji Koncepcji, która jest częścią C ++ 20. Jako takie, jedynymi typami symboli zastępczych w C ++ 20 są auto(i ich odmiany podobne auto**) decltype(auto)oraz ograniczone symbole zastępcze ( Concept autoi ich odmiany). Ten rodzaj zagnieżdżonego typu symbolu zastępczego byłby bardzo przydatny, ale nie jest częścią C ++ 20, więc deklaracja funkcji jest źle sformułowana.

Teraz gcc pozwala na to, ponieważ gcc zaimplementował Concepts TS i myślę, że postanowili zachować tę funkcję. clang nigdy nie wdrożył TS, więc nie robi tego.

Tak czy inaczej:

std::pair<auto, auto> f() { return {1, 2}; }

Zawsze byłby źle uformowany. Znaczenie składni polega na tym, że dedukujemy typ zwracany, a następnie wymagamy, aby pasował pair<T, U>do niektórych typów Ti U. Zasadniczo próbujemy wywołać wymyśloną funkcję:

template <typename T, typename U>
void __f(std::pair<T, U>);

__f({1, 2}); // this must succeed

Ale nie można wydedukować typu na podstawie {1, 2}- lista stężonych inicjacji nie ma typu. Być może jest to coś, co należy zbadać (ponieważ łatwo to zrozumieć przynajmniej w tak prostym przypadku jak ten), ale nigdy nie było to dozwolone. Więc odrzucenie jest poprawne w obu przypadkach.

W końcu:

gcc 9.2 wydaje się wierzyć, że std::pair<auto, auto>jest to int. Jak można to wyjaśnić?

Z jakiegoś powodu (prawdopodobnie z powodu naszej dorozumianej wersji C int), gdy gcc nie rozpoznaje ani nie rozumie typu, po prostu używa go intjako symbolu zastępczego w komunikatach o błędach. Jest to bardzo mylące, ponieważ oczywiście powstał gcc, inta nie kod źródłowy. Ale tak to już jest.

Barry
źródło
„Braced-init-list nie ma argumentu typu” nie jest dla mnie jasne. std :: pair <int, int> f () {return {1,2}; } działa, a {1,2} nie ma typu (wywołuje konstruktor std :: pair <int, int> w moim rozumieniu). Może z <auto, auto> kompilator nie może wydedukować typów 1 i 2 z listy inicjalizacyjnej {1, 2}?
mfnx
@mfnx Nie ma argumentu typu , po prostu nie ma typu. Usztywnione listy inicjujące mogą być używane tylko w niektórych sytuacjach - na przykład podczas inicjowania znanego typu. Ale nie można ich użyć do odliczenia - ponieważ nie mają typu. Z wyjątkiem auto x = {1, 2};działa, ale tylko wtedy, gdy wszystkie typy są takie same.
Barry
2
Większość kompilatorów zamiast zatrzymywać się przy pierwszym błędzie, próbuje je odzyskać, aby zgłosić dodatkowe błędy. Ogólnie oznacza to założenie, że wszystko, czego nie można oddzielić, jest int. To nie intjest symbol zastępczy w komunikatach o błędach; kompilator naprawdę myśli, że to int. (Aby to wyjaśnić, gcc powinien był kiedyś powiedzieć „zakładając int”.)
Raymond Chen
2
Zauważ, że inną możliwą drogą rozszerzenia byłoby umożliwienie odliczenia argumentu szablonu klasy dla typów zwracanych, ponieważ std::pair __f{1,2};działa.
Davis Herring
2
@ DavisHerring Naprawdę nie chciałbym mieć std::optional f() { return 4; }pracy.
Barry