and - constexpr in c++
enum vs constexpr para constantes estáticas reales dentro de las clases (6)
Del estándar C ++ N3797 S3.5 / 2-3
Se dice que un nombre tiene un vínculo cuando puede denotar el mismo objeto, referencia, función, tipo, plantilla, espacio de nombre o valor como un nombre introducido por una declaración en otro ámbito:
- Cuando un nombre tiene un enlace externo, la entidad a la que denota se puede referir por los nombres de los ámbitos de otras unidades de traducción o desde otros ámbitos de la misma unidad de traducción.
- Cuando un nombre tiene un enlace interno, la entidad a la que denota se puede referir con nombres de otros ámbitos en la misma unidad de traducción.
- Cuando un nombre no tiene un vínculo, la entidad a la que denota no puede ser referenciada por nombres de otros ámbitos.
Un nombre que tiene un ámbito de espacio de nombres (3.3.6) tiene un enlace interno si es el nombre de
- una variable, función o plantilla de función que se declara explícitamente estática; o,
- una variable no volátil que se declara explícitamente const o constexpr y ni explícitamente declarada extern ni declarada previamente para tener un enlace externo; o
- un miembro de datos de una unión anónima.
Mi lectura es esa en el siguiente código:
public:
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;
};
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;
Las 4 instancias de SOME_VALUE
tienen enlaces internos. Deberían enlazar con una referencia a SOME_VALUE
en la misma unidad de traducción y no ser visibles en ningún otro lugar.
Obviamente, el primero es una declaración y no una definición. Necesita una definición dentro de la misma unidad de traducción. Si GCC lo dice y MSVC no, entonces MSVC está equivocado.
A los efectos de reemplazar una enumeración, el número 2 debería funcionar bien. Todavía tiene enlaces internos sin la palabra clave static
.
[Editado en respuesta al comentario]
Permítanme comenzar declarando mi intención. En los viejos tiempos (C ++), tendríamos código como:
class C
{
public:
enum {SOME_VALUE=27};
};
Entonces podríamos usar SOME_VALUE
todo nuestro código como una constante de tiempo de compilación y donde el compilador vería C::SOME_VALUE
, simplemente insertaría el literal 27.
Hoy en día, parece más aceptable cambiar ese código a algo así como:
class C
{
public:
static constexpr int SOME_VALUE=27;
};
Esto se ve mucho más limpio, le da a SOME_VALUE
un tipo bien definido y parece ser el enfoque preferido a partir de C ++ 11. El problema (imprevisto al menos para mí) es que esto también provoca escenarios en los que SOME_VALUE
debe ser externo. Es decir, en algún archivo cpp en alguna parte, necesitamos agregar:
constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage
Los casos que causan esto parecen ser cuando se utilizan referencias a SOME_VALUE
, lo que sucede muy a menudo en el código de la Biblioteca Estándar de C ++ (ver el ejemplo al final de esta pregunta). Estoy usando gcc 4.7.2 como mi compilador por cierto.
Debido a este dilema, me veo obligado a volver a definir SOME_VALUE
como una enumeración (es decir, la vieja escuela) para evitar tener que agregar una definición a un archivo cpp para algunas, pero no todas mis variables estáticas de constexto. ¿No hay alguna manera de decirle al compilador que constexpr int SOME_VALUE=27
significa que SOME_VALUE
debe tratarse solo como una constante de tiempo de compilación y nunca un objeto con enlace externo? Si ve una referencia constante utilizada con ella, cree una temporal. Si ve su dirección tomada, genere un error de tiempo de compilación si eso es lo que necesita, porque es una constante de tiempo de compilación y nada más.
Aquí hay un código de muestra aparentemente benigno que hace que necesitemos agregar la definición para SOME_VALUE
en un archivo cpp (una vez más, probado con gcc 4.7.2):
#include <vector>
class C
{
public:
static constexpr int SOME_VALUE=5;
};
int main()
{
std::vector<int> iv;
iv.push_back(C::SOME_VALUE); // Will cause an undefined reference error
// at link time, because the compiler isn''t smart
// enough to treat C::SOME_VALUE as the literal 5
// even though it''s obvious at compile time
}
Agregar la siguiente línea al código en el alcance del archivo resolverá el error:
constexpr int C::SOME_VALUE;
Hoy en día, la forma preferida es:
enum class : int C { SOME_VALUE = 5 };
Para el registro, la versión static constexpr
funcionará como esperabas en C ++ 17. Del N4618 Anexo D.1 [depr.static_constexpr] :
D.1 Redeclaración de miembros de datos
static constexpr
[depr.static_constexpr]Para la compatibilidad con estándares internacionales previos de C ++, un miembro de datos estáticos
constexpr
puede ser redeclarado redundantemente fuera de la clase sin inicializador. Este uso está en desuso. [ Ejemplo:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
- ejemplo final ]
El texto estándar relevante que permite esto es N4618 9.2.3 [class.static.data]/3 :
[...] Un miembro de datos estáticos en línea puede definirse en la definición de clase y puede especificar un inicializador de llave o igual . Si el miembro se declara con el especificador de
constexpr
, se puede volver a declarar en el ámbito del espacio de nombres sin inicializador (este uso está en desuso, ver D.1). [...]
Esto viene con la misma maquinaria que introdujo la versión no constexpr
de lo mismo, miembros de datos estáticos en línea .
struct A {
static inline int n = 5; // definition (illegal in C++ 2014)
};
inline int A::n; // illegal
Puedes hacerlo
class C
{
public:
static const int SOME_VALUE=5;
};
int main()
{
std::vector<int> iv;
iv.push_back(C::SOME_VALUE);
}
Esto ni siquiera es C ++ 11, solo C ++ 98
Tienes tres opciones aquí:
Si su clase es plantilla, coloque la definición de miembro estático en el encabezado en sí. Se requiere que el compilador lo identifique como una única definición en varias unidades de traducción (ver [basic.def.odr] / 5)
Si su clase no tiene plantilla, puede ponerla fácilmente en el archivo fuente
De forma alternativa, declare la función de miembro estático constexpr getSomeValue ():
class C { public: static constexpr int getSomeValue() { return 27; } };
Yo iría con la clase enum:
http://en.cppreference.com/w/cpp/language/enum
http://www.stroustrup.com/C++11FAQ.html#enum
Desde el primer enlace:
enum class Color { RED, GREEN=20, BLUE};
Color r = Color::BLUE;
switch(r) {
case Color::RED : std::cout << "red/n"; break;
case Color::GREEN : std::cout << "green/n"; break;
case Color::BLUE : std::cout << "blue/n"; break;
}
// int n = r; // error: no scoped enum to int conversion
int n = static_cast<int>(r); // OK, n = 21