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

16

Estaba jugando con autoadentro std::pair. En el siguiente código, fse supone que la función devuelve una std::pairde los tipos que dependen de un parámetro de plantilla.

Un ejemplo de trabajo:

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

Esto funciona con gcc 9.2, gcc 10.0, clang 9.0 y clang 10.0.

A continuación, quería escribir explícitamente el tipo de retorno como a std::pairpor razones de claridad:

EJEMPLO 2

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

Tanto gcc 9.2 / 10.0 como clang 9.0 / 10.0 no pudieron compilar esto.

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

Desde el último mensaje de error, gcc 9.2 parece creer que std::pair<auto, auto>es un int. ¿Cómo se puede explicar esto?

gcc 10.0

error: returning initializer list

Este error es comprensible, sin embargo, esperaba std::pairque se invocara al constructor o ¿hay algo que me falta aquí?

Clang 9.0 y 10.0

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

Ok, a clang no le gusta nada de esto. Desde el segundo mensaje de error, parece que clang también cree que el tipo de retorno es int.

Finalmente, para corregir el error obtenido al compilar con gcc 10.0, decidí devolver std::pairexplícitamente:

EJEMPLO 3

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

Clang 9.0 y 10.0

Igual que antes, pero con un adicional:

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

Aquí el sonido metálico todavía piensa que estamos regresando un int?

gcc 9.2

Igual que antes.

gcc 10.0

¡Funciona!

Supongo que algunas características aún tienen que implementarse, o en una de las situaciones descritas anteriormente, ¿hay un compilador correcto y el otro incorrecto? En mi opinión, el ejemplo 2 debería funcionar. ¿O no debería?

mfnx
fuente

Respuestas:

23

La sintaxis:

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

Formaba parte del TS de Concepts original, pero no estaba incluido en la propuesta de Concepts que forma parte de C ++ 20. Como tal, los únicos tipos de marcador de posición en C ++ 20 son auto(y sus variaciones son similares auto**) decltype(auto)y los marcadores de posición restringidos ( Concept autoy sus variaciones). Este tipo de marcador de posición anidado sería muy útil, pero no forma parte de C ++ 20, por lo que esa declaración de función está mal formada.

Ahora, gcc lo permite porque gcc implementó el Concepts TS y supongo que decidieron mantener esta característica. clang nunca implementó el TS, por lo que no lo hace.

De cualquier manera, esto:

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

Siempre estaría mal formado. El significado de la sintaxis es que deducimos el tipo de retorno y luego requerimos que coincida pair<T, U>con algunos tipos Ty U. Básicamente estamos tratando de invocar la función inventada:

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

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

Pero no puede deducir un tipo de {1, 2}: una lista-init-braced no tiene un tipo. Quizás esto es algo que debería explorarse (ya que es fácil de entender al menos en un caso simple como este), pero nunca se ha permitido. Entonces rechazarlo es correcto de cualquier manera.

Finalmente:

gcc 9.2 parece creer que std::pair<auto, auto>es un int. ¿Cómo se puede explicar esto?

Por alguna razón (probablemente debido a nuestro legado C con implícito int), cuando gcc no reconoce o no comprende un tipo, solo lo utiliza intcomo marcador de posición en los mensajes de error. Esto es súper confuso, porque obviamente surgió gcc inty no el código fuente. Pero así son las cosas.

Barry
fuente
La "lista-init-braced no tiene un argumento de tipo" no me resulta clara. std :: pair <int, int> f () {return {1,2}; } funciona, y {1,2} no tiene tipo (invoca el constructor de std :: pair <int, int> tal como lo entiendo). ¿Quizás con <auto, auto>, el compilador no puede deducir los tipos de 1 y 2 en la lista de inicializadores {1, 2}?
mfnx
@mfnx No no tiene un argumento de tipo , simplemente no tiene un tipo. las listas de inicialización entre paréntesis solo se pueden usar en ciertas situaciones, como inicializar un tipo conocido. Pero no se pueden usar en la deducción, porque no tienen ningún tipo. Excepto que auto x = {1, 2};funciona, pero solo si todos los tipos son iguales.
Barry
2
La mayoría de los compiladores, en lugar de detenerse en el primer error, intentan recuperarse para poder informar errores adicionales. Esto generalmente significa asumir que todo lo que no se puede analizar es un int. No es que intsea ​​un marcador de posición en los mensajes de error; el compilador realmente piensa que es un int. (Para aclarar esto, gcc probablemente debería haber dicho "asumiendo int" en algún momento).
Raymond Chen
2
Tenga en cuenta que otra posible vía de extensión sería permitir la deducción de argumentos de plantilla de clase para los tipos de retorno, ya que std::pair __f{1,2};funciona.
Davis Herring
2
@DavisHerring Realmente no me gustaría tener std::optional f() { return 4; }trabajo.
Barry