Tenemos una aplicación mucho más grande que se basa en la sobrecarga de plantillas de matrices char y const char. En gcc 7.5, clang y visual studio, el siguiente código imprime "NO CONST" para todos los casos. Sin embargo, para gcc 8.1 y posterior, la salida se muestra a continuación:
#include <iostream>
class MyClass
{
public:
template <size_t N>
MyClass(const char (&value)[N])
{
std::cout << "CONST " << value << '\n';
}
template <size_t N>
MyClass(char (&value)[N])
{
std::cout << "NON-CONST " << value << '\n';
}
};
MyClass test_1()
{
char buf[30] = "test_1";
return buf;
}
MyClass test_2()
{
char buf[30] = "test_2";
return {buf};
}
void test_3()
{
char buf[30] = "test_3";
MyClass x{buf};
}
void test_4()
{
char buf[30] = "test_4";
MyClass x(buf);
}
void test_5()
{
char buf[30] = "test_5";
MyClass x = buf;
}
int main()
{
test_1();
test_2();
test_3();
test_4();
test_5();
}
La salida de gcc 8 y 9 (de godbolt) es:
CONST test_1
NON-CONST test_2
NON-CONST test_3
NON-CONST test_4
NON-CONST test_5
Esto me parece un error del compilador, pero supongo que podría ser algún otro problema relacionado con un cambio de idioma. ¿Alguien sabe definitivamente?
Respuestas:
Cuando devuelve una expresión de identificación simple de una función (que designó un objeto local de función), el compilador tiene el mandato de hacer la resolución de sobrecarga dos veces. Primero lo trata como si fuera un valor, y no un valor. Solo si falla la primera resolución de sobrecarga, se volverá a realizar con el objeto como un valor l.
Si tuviéramos que agregar una sobrecarga de valor,
la salida se convertirá
Y esto sería correcto. Lo que no es correcto es el comportamiento de GCC tal como lo ves. Considera que la primera resolución de sobrecarga es un éxito. Esto se debe a que una referencia de valor constante puede unirse a un valor r. Sin embargo, ignora el texto "o si el tipo del primer parámetro del constructor seleccionado no es una referencia de valor al tipo del objeto" . De acuerdo con eso, debe descartar el resultado de la primera resolución de sobrecarga y volver a hacerlo.
Bueno, esa es la situación hasta C ++ 17 de todos modos. El borrador estándar actual dice algo diferente.
Se eliminó el texto de hasta C ++ 17. Entonces es un error de viaje en el tiempo. GCC implementa el comportamiento de C ++ 20, pero lo hace incluso cuando el estándar es C ++ 17.
fuente
clang++
la implementación de C ++ 20 tampoco es correcta ya que usa la versión NO CONST en todos los casos en el código original.Hay un debate sobre si esto es o no "comportamiento intuitivo" en los comentarios, por lo que pensé en apostar por el razonamiento detrás de este comportamiento.
Hubo una charla bastante agradable en CPPCON que me deja un poco más claro { talk , slides }. Básicamente, ¿qué implica una función que toma una referencia no constante? Que el objeto de entrada debe ser de lectura / escritura . Aún más fuerte, implica que tengo la intención de modificar este objeto, esta función tiene efectos secundarios . Una referencia constante implica solo lectura , y una referencia de valor significa que puedo tomar los recursos . Si
test_1()
terminara llamando alNON-CONST
constructor, significaría que tengo la intención de modificar este objeto, aunque después de que haya terminado, ya no exista,lo cual (creo) sería un error (estoy pensando en un caso en el que la vinculación de una referencia durante la inicialización depende de si el argumento pasado es constante o no).Lo que me preocupa un poco más es la sutileza introducida por
test_2()
. Aquí, se está llevando a cabo la inicialización de la lista de copias en lugar de las reglas relativas a [class.copy.elision] citadas anteriormente. Ahora realmente está diciendo que devuelva un objeto de tipo MyClass como si lo hubiera inicializadobuf
, por lo queNON-CONST
se invoca el comportamiento. Siempre he pensado en las listas de inicio como formas de ser más concisas, pero aquí las llaves hacen una diferencia semántica significativa. Esto importaría más si los constructoresMyClass
tomaran una gran cantidad de argumentos. Luego, digamos que desea crear unbuf
, modificarlo, luego devolverlo con la gran cantidad de argumentos, invocando elCONST
comportamiento. Por ejemplo, digamos que tienes los constructores:Y prueba:
Godbolt nos dice que obtenemos un
NON-CONST
comportamiento, aunqueCONST
probablemente sea lo que queremos (después de haber bebido la buena ayuda en la semántica de argumentos de función). Pero ahora la inicialización de la lista de copias no hace lo que nos gustaría. El siguiente tipo de prueba mejora mi punto:Ahora para obtener la semántica adecuada con la inicialización de la lista de copias, el búfer debe ser "rebote" al final. Supongo que si el objetivo fuera que este objeto fuera inicializar algún otro
MyClass
objeto, solo usar elNON-CONST
comportamiento en la lista de copias de retorno estaría bien si el constructor move / copy invocara el comportamiento apropiado, pero eso está comenzando a sonar bastante delicado.fuente