enum vs constexpr para constantes estáticas reales dentro de clases


Permítanme comenzar declarando mi intención. En los días de olden (C++), tendríamos código como:

class C
{
public:
  enum {SOME_VALUE=27};
};

Entonces podríamos usar SOME_VALUE a lo largo de nuestro código como una constante de tiempo de compilación y donde quiera que el compilador vea C::SOME_VALUE, simplemente insertaría el literal 27.

Hoy en día, parece más aceptable cambiar ese código a algo como: {[15]]}

class C
{
public:
  static constexpr int SOME_VALUE=27;
};

Esto parece mucho más limpio, da SOME_VALUE un tipo bien definido y parece ser el enfoque preferido a partir de C++11. El (imprevisto al menos para mí) el problema es que esto también causa escenarios donde SOME_VALUE necesita hacerse externo. Es decir, en algún archivo cpp en algún lugar, necesitamos agregar:

constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage

Los casos que causan esto parecen ser cuando se usan referencias const a SOME_VALUE, lo que sucede muy a menudo en el código de la biblioteca estándar de C++ (Vea 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, de la vieja escuela) para evitar tener que añadir una definición a un archivo cpp para algunas, pero no para todas mis variables miembro estáticas constexpr. ¿No hay alguna manera de decirle al compilador que constexpr int SOME_VALUE=27 significa que SOME_VALUE debe ser tratado solo como una constante de tiempo de compilación y nunca como un objeto con enlace externo? Si ve una referencia const utilizada con ella, cree una temporal. Si ves su dirección tomada, genera un error de tiempo de compilación si eso es lo que se necesita, porque es una compilación constante de tiempo y nada más.

Aquí hay un código de muestra aparentemente benigno que hace que necesitemos agregar la definición de 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 ámbito del archivo resolverá el error:

constexpr int C::SOME_VALUE;
Author: jww, 2014-04-04

6 answers

Para el registro, la versión static constexpr funcionará como esperabas en C++17. De N4618 Anexo D. 1 [depr.static_constexpr]:

D. 1 Redeclaración de static constexpr miembros de datos [depr.static_constexpr]

Para la compatibilidad con estándares internacionales anteriores de C++, un miembro de datos estáticos constexpr puede ser redundantemente redeclarado fuera de la clase sin inicializador. Este uso está obsoleto. [Ejemplo:

struct A {
 static constexpr int n = 5; // definition (declaration in C++ 2014)
};

constexpr int A::n; // redundant declaration (definition in C++ 2014)

-fin ejemplo]

El texto estándar relevante que permite esto es N4618 9.2.3 [clase.estática.datos]/3:

[...] Un miembro de datos estáticos en línea puede definirse en la definición de clase y puede especificar un inicializador brace-or-equal. Si el miembro se declara con el especificador constexpr, puede volver a declararse en el ámbito del espacio de nombres sin inicializador (este uso está obsoleto; consulte D. 1). [...]

Esto viene con la misma maquinaria que se introdujo la versión no-constexpr de la misma cosa, 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
 6
Author: TBBle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:46:50

Aquí tienes tres opciones:

  1. Si su clase es template, ponga la definición de miembro estático en el encabezado mismo. Se requiere que el compilador lo identifique como una sola definición a través de múltiples unidades de traducción (ver [basic.def.odr]/5)

  2. Si su clase no es una plantilla, puede colocarla fácilmente en el archivo fuente

  3. Alternativamente declare constexpr static member function getSomeValue ():

    class C
    {
    public:
        static constexpr int getSomeValue() { return 27; }
    };
    
 8
Author: apoorvkul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-27 04:00:04

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
 2
Author: Fernando A. Gómez F.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-28 18:53:22

Hoy en día, la forma preferida es:

enum class : int C { SOME_VALUE = 5 };
 1
Author: edwinc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-15 16:50:29

Del estándar C++ N3797 S3. 5/2-3

Se dice que un nombre tiene enlace cuando puede denotar el mismo objeto, referencia, función, tipo, plantilla, espacio de nombres o valor que un nombre introducido por una declaración en otro ámbito:

- Cuando un nombre tiene enlace externo , la entidad que denota puede ser referida por nombres de ámbitos de otras unidades de traducción o de otros ámbitos de la misma unidad de traducción.

- Cuando un nombre tiene enlace interno, la entidad denotes puede ser referido por nombres de otros ámbitos en la misma unidad de traducción.

- Cuando un nombre no tiene vinculación , la entidad que denota no puede ser referida por nombres de otros ámbitos.

Un nombre con ámbito de espacio de nombres (3.3.6) tiene 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 explicitly declared extern nor previously declared to have external linkage; or

- un miembro de datos de un sindicato anónimo.

Mi lectura es que 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 enlace interno. Deben enlazar con una referencia a SOME_VALUE en la misma unidad de traducción y no ser visibles en otra parte.

, Obviamente, la primera es una declaración y no una definición. Necesita una definición dentro de la misma unidad de traducción. If GCC lo dice y MSVC no lo hace, entonces MSVC está equivocado.

A los efectos de reemplazar una enumeración, el número 2 debería funcionar bien. Todavía tiene enlace interno sin la palabra clave static.

[Editado en respuesta al comentario]

 1
Author: david.pfx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-16 23:17:52

Puedes hacer esto

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

 -1
Author: Ophir Gvirtzer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-07 16:21:51