std :: pair <auto, auto> tipo de retorno

16

Eu estava brincando com autono std::pair. No código abaixo, a função fdeve retornar um std::pairdos tipos que dependem de um parâmetro de modelo.

Um exemplo de trabalho:

EXEMPLO 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
}

Isso funciona com o gcc 9.2, gcc 10.0, clang 9.0 e clang 10.0.

Em seguida, eu queria escrever explicitamente o tipo de retorno como um std::pairmotivo de clareza:

EXEMPLO 2

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

O gcc 9.2 / 10.0 e o clang 9.0 / 10.0 falharam ao compilar isso.

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

Da última mensagem de erro, o gcc 9.2 parece acreditar que std::pair<auto, auto>é um int. Como isso pode ser explicado?

gcc 10.0

error: returning initializer list

Esse erro é compreensível, no entanto, eu esperava que o construtor std::pairfosse invocado ou há algo que estou faltando aqui?

clang 9.0 e 10.0

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

Ok, clang não gosta de nada disso. Na segunda mensagem de erro, parece que o clang também acredita que é o tipo de retorno int.

Por fim, para corrigir o erro obtido na compilação com o gcc 10.0, decidi retornar um std::pairexplicitamente:

EXEMPLO 3

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

clang 9.0 e 10.0

O mesmo de antes, mas com um adicional:

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

Aqui clang ainda pensa que estamos retornando um int?

gcc 9.2

O mesmo de antes.

gcc 10.0

Funciona!

Eu acho que alguns recursos ainda precisam ser implementados, ou em uma das situações descritas acima, existe um compilador certo e o outro errado? Na minha opinião, o exemplo 2 deve funcionar. Ou não deveria?

mfnx
fonte

Respostas:

23

A sintaxe:

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

Fazia parte do TS original de conceitos, mas não foi incluído na proposta de conceitos que faz parte do C ++ 20. Como tal, os únicos tipos de espaços reservados em C ++ 20 são auto(e variações semelhantes auto**) decltype(auto)e espaços reservados restritos ( Concept autoe variações). Esse tipo de espaço reservado aninhado seria muito útil, mas não faz parte do C ++ 20, para que a declaração da função seja mal formada.

Agora, o gcc permite isso porque o gcc implementou o TS Concepts e acho que eles decidiram manter esse recurso. O clang nunca implementou o TS, então não.

De qualquer maneira, isto:

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

Sempre seria mal formado. O significado da sintaxe é que deduzimos o tipo de retorno e exigimos que ela corresponda pair<T, U>a alguns tipos Te U. Estamos basicamente tentando invocar a função inventada:

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

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

Mas você não pode deduzir um tipo de {1, 2}- uma lista-init-braced não possui um tipo. Talvez isso seja algo que deva ser explorado (como é fácil entender pelo menos em um caso simples como esse), mas nunca foi permitido. Portanto, rejeitá-lo é correto de qualquer maneira.

Por último:

O gcc 9.2 parece acreditar que std::pair<auto, auto>é um int. Como isso pode ser explicado?

Por alguma razão (provavelmente devido ao nosso legado C com implícito int), quando o gcc não reconhece ou entende um tipo, ele apenas usa intcomo espaço reservado nas mensagens de erro. Isso é super confuso, porque obviamente é o gcc que surgiu inte não o código fonte. Mas é assim que as coisas são.

Barry
fonte
O "braced-init-list não tem um argumento de tipo" não está claro para mim. std :: pair <int, int> f () {return {1,2}; } funciona e {1,2} não tem tipo (ele chama o construtor de std :: pair <int, int> como eu o entendo). Talvez com <auto, auto>, o compilador não consiga deduzir os tipos 1 e 2 na lista de inicializadores {1, 2}?
mfnx 3/01
@mfnx Not não possui um argumento de tipo , apenas não possui um tipo. as listas init iniciadas somente podem ser usadas em determinadas situações - como inicializar um tipo conhecido. Mas eles não podem ser usados ​​em dedução - porque não têm tipo. Exceto auto x = {1, 2};funciona, mas apenas se todos os tipos forem iguais.
Barry
2
A maioria dos compiladores, em vez de apenas parar no primeiro erro, tenta se recuperar dele para que eles possam relatar erros adicionais. Isso geralmente significa assumir que tudo o que não é analisável é um int. Não inté um espaço reservado nas mensagens de erro; o compilador realmente pensa que é um int. (Para deixar isso mais claro, o gcc provavelmente deveria ter dito "assumindo int" em algum momento.)
Raymond Chen
2
Observe que outro caminho possível para a extensão seria permitir a dedução do argumento do modelo de classe para tipos de retorno, pois std::pair __f{1,2};funciona.
Davis Herring
2
@DavisHerring Eu realmente não gostaria de ter std::optional f() { return 4; }trabalho.
Barry