c++ - otro - Clang y GCC frente a MSVC e ICC: ¿Es necesario un static_assert en el constructor de copiar/mover para que funcione, si se puede aplicar elision de copiar/mover?
copiar un archivo en lenguaje c (4)
Tengo un static_assert
en un constructor de movimiento de una estructura de plantilla mía. ¿ static_assert
necesario que static_assert
sea considerado por el compilador, incluso si la copia es posible?
Este es el escenario simplificado:
#include <type_traits>
template<typename T>
struct X
{
X(X&&) { static_assert(std::is_same<void, T>::value, "Intentional Failure"); }
};
auto impl() -> X<int>;
auto test() -> decltype(impl())
{
return impl();
}
int main()
{
test();
}
GCC y Clang acuerdan evaluar el static_assert
y no compilar.
MSCV y ICC, por otro lado, compilan el código muy bien.
Curiosamente, cuando elimino la definición del constructor de move
y simplemente lo declaro así:
template<typename T>
struct X
{
X(X&&);
};
GCC y Clang también compilan el código ahora. Por lo tanto, todos los compiladores parecen estar de acuerdo en que la definición del constructor de movimiento es irrelevante para la elección de copia.
Pregunta :
Si hay un static_assert
en el constructor de copiar / mover, ¿el estándar requiere que se evalúe incluso si es posible copiar / mover elision?
Creo que la respuesta es no. Mi lógica es así:
- Elision de copia requiere la declaración de constructores de copia / movimiento pero no requiere definición.
- Las definiciones de función de miembro de las plantillas no se crean instancias a menos que se requieran sus definiciones.
- Si no se crea una instancia de una definición, no se puede probar si está mal formada.
Referencias:
14.7.1.1 ... La creación de instancias implícita de una especialización de plantilla de clase provoca la creación de instancias implícita de las declaraciones, pero no de las definiciones, argumentos predeterminados o especificaciones de excepción de las funciones de miembro de clase ...
14.7.1.2 A menos que un miembro de una plantilla de clase ... haya sido explícitamente instanciado o explícitamente especializado, la especialización del miembro se crea implícitamente cuando se hace referencia a la especialización en un contexto que requiere que exista la definición del miembro ...
Creo que la respuesta es sí.
primero el static_assert obliga al constructor de "definición" a una declaración.
No estoy seguro exactamente acerca de la naturaleza de la plantilla de static_assert con respecto a la sección 12.8 a continuación, ya sea ...
(Me disculpo por el formato ...)
c © ISO / IEC N3242 = 11-0012 7 Declaraciones [dcl.dcl]
2. Una declaración es una definición a menos que declare una función sin especificar el cuerpo de la función (8.4), contiene el especificador externo (7.1.1) o una especificación de vinculación 25 (7.5) y ni un inicializador ni un cuerpo de función, declara un miembro de datos estáticos en una definición de clase (9.4), es una declaración de nombre de clase (9.1), es una declaración de enumeración opaca (7.2), o es una declaración typedef (7.1.3), un uso -declaración (7.3.3), una static_assert-statement (Cláusula 7), una atributo-declaración (Cláusula 7), una vacía-declaración (Cláusula 7), o una directiva de uso (7.3.4)
12.8 Copiar y mover objetos de clase [class.copy]
7 Una plantilla de función miembro nunca se crea una instancia para realizar la copia de un objeto de clase a un objeto de su tipo de clase. [Ejemplo:
struct S {
template<typename T> S(T);
template<typename T> S(T&&);
S();
};
S f();
const S g;
void h() {
S a( f() );// does not instantiate member template;
// uses the implicitly generated move constructor
S b(g);// does not instantiate the member template;
// uses the implicitly generated copy constructor
}
- ejemplo final]
32 Cuando se cumplen ciertos criterios, una implementación puede omitir la construcción de copiar / mover de un objeto de clase, incluso si el constructor y / o destructor de copia / movimiento del objeto tiene efectos secundarios. En tales casos, la implementación trata el origen y el destino de la operación de copia / movimiento omitida simplemente como dos formas diferentes de referirse al mismo objeto, y la destrucción de ese objeto ocurre en el último momento en que los dos objetos hubieran sido Destruido sin la optimización. 123 - Esta elision de operaciones de copia / movimiento, llamada copia de elision, se permite en las siguientes circunstancias (que pueden combinarse para eliminar múltiples copias): - en una declaración de retorno en una función con un tipo de retorno de clase, cuando la expresión es la nombre de un objeto automático no volátil (que no sea una función o un parámetro de cláusula catch) con el mismo tipo no calificado cv que el tipo de retorno de función, la operación de copiar / mover se puede omitir construyendo el objeto automático directamente en el retorno de la función valor
Lo siguiente debería ayudar.
No tiene que emplear la deducción de tipo para ilustrar el problema. Incluso el ejemplo más simple tiene el mismo problema:
#include <type_traits>
template <typename T>
struct X
{
X() {}
X(X&&) { static_assert(std::is_same<void, T>::value, "failed"); }
};
int main()
{
X<int> x = X<int>();
}
Clang y GCC no lo compilarán. MSVC compila y ejecuta bien.
Esto muestra que el problema está relacionado con el uso de odr y cuando se crean instancias de las funciones de los miembros.
14.7.1 [temp.inst] el párrafo 2 dice "[...] la especialización del miembro se crea implícitamente cuando se hace referencia a la especialización en un contexto que requiere que exista la definición del miembro"
3.2 [basic.def.odr] el párrafo 3 dice (en una nota) "[...] un constructor seleccionado para copiar o mover un objeto de tipo de clase se usa de forma estándar incluso si la implementación es realmente eluida por la implementación"
3.2 [basic.def.odr] el párrafo 4 dice: "Cada programa contendrá exactamente una definición de cada función o variable no en línea que se usa en ese programa; no se requiere diagnóstico".
Ergo: la especialización debe ser instanciada, y la afirmación se ha disparado.
Los constructores de movimiento no son llamados. static_assert
se evalúa en la instanciación de X<int>::X(X&&)
. Lo más probable es que algunos compiladores evalúen los métodos de la plantilla cuando se usan (cuando usa el constructor de movimiento y no lo usa), y otros: al crear una instancia de la plantilla de la clase (cuando usa X<int>
).