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
constexpr
falta enx2
ydata
,const
engetData
. godbolt.org/z/ZNL2BKRespuestas:
El problema es que
boost::hana::tuple
no 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::tuple
no tiene un constructor de copia, uno se declara implícitamente y se define como predeterminado (no se suprime yaboost::hana::tuple
que 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
#1
está 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::tuple
manera 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.2
parte aceptada por Clang? (Edité la pregunta original y dividí test2 en test2.1 y test2.2) Esperaba que se comportaran igual.hana::tuple
que 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
decltype
ay 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 guardarlodecltype
porque 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
x1
que es de tipoX
especializado con hana :: integral_constant, ya que el resultado de1_c
es 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 althis
puntero no constantex1
es necesario).Esto es muy diferente para su contenedor con el
hana::tuple
cual contiene una construcción de copia dehana::tuple
datos desde elx2.data
campo. Esto requiere acceso factual a suthis
puntero, que no era necesario en el caso dex1
que tampoco era una variable constexpr.Esto significa que está expresando su intención equivocada con ambos
x1
yx2
, 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
x1
sea un tipo vacío. Cualquier instancia deX
tiene un miembro de datos. Tambiénhana::tuple
contiene 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 expression
const this
puntero y desafortunadamente lo esté utilizandox2
en el caso static_assert. (en el caso de x1, es una discusión adicional:)).hana::integral_constant
tiene un constructor predeterminado, definido por el compilador,hana::tuple
tiene 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