Estoy usando c ++ 17 con Boost.hana para escribir algunos programas de metaprogramación. Un problema que me atrapó es qué tipo de expresión se puede usar en un contexto constexpr como static_assert. Aquí hay un ejemplo:
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() {
return data;
}
};
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2.1
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
// static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
}
{ //test2.2
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
}
Primero escribo una clase X con datos de campo y un descriptor de acceso getData () . En el main () 's test1 parte, x1.data y x1.getData () se comportan igual que yo esperaba. Pero en la parte test2 , cambiar el argumento a un boost :: la tupla de hana, static_assert(x2.data[0_c] == 1_c)todavía se comporta bien pero static_assert(x2.getData()[0_c] == 1_c)falla la compilación, con el error de ' leer la variable no constexpr' x2 'no está permitido en una expresión constante '. Lo que es weired si divido x2.getData()[0_c]en auto data = x2.getData();y static_assert(data[0_c] == 1_c);compila bien de nuevo. Esperaría que se comporten igual. Entonces, ¿alguien puede ayudar a explicar por qué x2.getData()[0_c]no se puede usar en static_assert en este ejemplo?
Para reproducir: clang ++ 8.0 -I / ruta / a / hana-1.5.0 / include -std = c ++ 17 Test.cpp
fuente

constexprfalta enx2ydata,constengetData. godbolt.org/z/ZNL2BKRespuestas:
El problema es que
boost::hana::tupleno tiene un constructor de copia.Tiene un constructor que se parece a un constructor de copia:
Pero como se trata de una plantilla, no es un constructor de copias .
Como
boost::hana::tupleno tiene un constructor de copia, uno se declara implícitamente y se define como predeterminado (no se suprime yaboost::hana::tupleque no tiene ningún constructor de copia ni movimiento ni operadores de asignación, porque, lo adivinó, no pueden ser plantillas).Aquí vemos divergencia de implementación , demostrada en el comportamiento del siguiente programa:
gcc acepta, mientras que Clang y MSVC rechazan, pero aceptan si la línea no
#1está comentada. Es decir, los compiladores no están de acuerdo sobre si el constructor de copias definido implícitamente de una clase no (directamente) vacía se puede usar dentro del contexto de evaluación constante.Según la definición del constructor de copia implícitamente definido, no hay forma de que el n. ° 1 sea diferente de
constexpr A(A const&) = default;modo que gcc sea correcto. Tenga en cuenta también que si le damos a B un constructor de copia constexpr definido por el usuario, Clang y MSVC nuevamente lo aceptan, por lo que el problema parece ser que estos compiladores no pueden rastrear la constructibilidad de la copia constexpr de clases recursivamente vacías implícitamente copiables. Errores archivados para MSVC y Clang ( corregidos para Clang 11).Tenga en cuenta que el uso de
operator[]es un arenque rojo; El problema es si los compiladores permiten la llamada agetData()(que construye copiasT) dentro de un contexto de evaluación constante comostatic_assert.Obviamente, la solución ideal sería que Boost.Hana corrija de
boost::hana::tuplemanera que tenga constructores de copia / movimiento reales y operadores de asignación de copia / movimiento. (Esto solucionaría su caso de uso ya que el código llamaría a constructores de copias proporcionados por el usuario, que están permitidos en un contexto de evaluación constante). Como solución alternativa , podría considerar pirateargetData()para detectar el caso de no estadoT:fuente
test2.2parte aceptada por Clang? (Edité la pregunta original y dividí test2 en test2.1 y test2.2) Esperaba que se comportaran igual.hana::tupleque se produce en el retorno degetData. En test2.2 la copia se produce fuera del contexto de evaluación constante, por lo que Clang está bien con ella.getData()no está permitido aquí, pero salir y presentar un temperal y luego aceptado ...El problema se debe a que está intentando recuperar un valor de tiempo de ejecución y probarlo en la compilación.
Lo que puede hacer es forzar la expresión en tiempo de compilación a través de
decltypeay funcionará como un encanto :).static_assert(decltype(x2.getData()[0_c]){} == 1_c);Ahora la expresión se evalúa en tiempo de compilación, por lo que el tipo se conoce en tiempo de compilación y, dado que también es construible en tiempo de cálculo, es posible usarlo dentro de un static_assert
fuente
static_assert(decltype(x2.getData()[0_c]){} == 1_c)puede funcionar bien, pero todavía quiero guardarlodecltypeporque eso ahorraría mucho. Supongo que está diciendo quex2.getData()está recuperando un valor de tiempo de ejecución, por lo que no debería aparecer en una expresión static_assert. Entonces no entiendo por qué lax1.getData()parte de prueba1 y los datos de ladata[0_c]parte de prueba2 pueden funcionar bien. ¿Cuáles son sus diferencias?Entonces, antes que nada, te falta el calificador const en el
getData()método, por lo que debería ser:No se promueve ninguna variable, al menos desde el punto de vista estándar, para ser constexpr si no está marcada como constexpr.
Tenga en cuenta que esto no es necesario para lo
x1que es de tipoXespecializado con hana :: integral_constant, ya que el resultado de1_ces un tipo sin constructor de copia definido por el usuario, que no contiene ningún dato internamente, por lo que una operación de copiagetData()es de hecho un no-op , por lo tanto, la expresión:static_assert(x1.getData() == 1_c);está bien, ya que no se ha realizado una copia real (ni el acceso althispuntero no constantex1es necesario).Esto es muy diferente para su contenedor con el
hana::tuplecual contiene una construcción de copia dehana::tupledatos desde elx2.datacampo. Esto requiere acceso factual a suthispuntero, que no era necesario en el caso dex1que tampoco era una variable constexpr.Esto significa que está expresando su intención equivocada con ambos
x1yx2, al menosx2, es necesario marcar estas variables como constexpr. Tenga en cuenta también que el uso de tuplas vacías, que es una especialización general básicamente vacía (sin constructores de copia definidos por el usuario)hana::tuple, funciona a la perfección (sección test3):fuente
x1sea un tipo vacío. Cualquier instancia deXtiene un miembro de datos. Tambiénhana::tuplecontiene tipos vacíos en sí mismo, ya que utiliza una optimización de base vacía. Es posible que esté en el destino culpando al constructor de la copia porque Clang o libc ++ podrían estar haciendo algo rarostd::integral_constant.constexpr variable 'x3' must be initialized by a constant expressionconst thispuntero y desafortunadamente lo esté utilizandox2en el caso static_assert. (en el caso de x1, es una discusión adicional:)).hana::integral_constanttiene un constructor predeterminado, definido por el compilador,hana::tupletiene uno definido por el usuario. Además, dado que hay una especialización para tuplas vacías, que no tiene ningún constructor, funciona el mismo código para tuplas vacías: godbolt.org/z/ZeEVQN