Hoy me encontré con un problema interesante. Considere este simple ejemplo:
template <typename T>
void foo(const T & a) { /* code */ }
// This would also fail
// void foo(const int & a) { /* code */ }
class Bar
{
public:
static const int kConst = 1;
void func()
{
foo(kConst); // This is the important line
}
};
int main()
{
Bar b;
b.func();
}
Al compilar me sale un error:
Undefined reference to 'Bar::kConst'
Ahora, estoy bastante seguro de que esto se debe a static const int
que no está definido en ninguna parte, lo cual es intencional porque, según tengo entendido, el compilador debería poder realizar el reemplazo en tiempo de compilación y no necesitar una definición. Sin embargo, dado que la función toma un const int &
parámetro, parece que no realiza la sustitución, sino que prefiere una referencia. Puedo resolver este problema realizando el siguiente cambio:
foo(static_cast<int>(kConst));
Creo que esto ahora está obligando al compilador a hacer un int temporal y luego pasar una referencia a eso, lo que puede hacer con éxito en tiempo de compilación.
Me preguntaba si esto fue intencional, o estoy esperando demasiado de gcc para poder manejar este caso. ¿O es algo que no debería estar haciendo por alguna razón?
const int kConst = 1;
el mismo resultado. Además, rara vez hay una razón (no puedo pensar en ninguna) para que una función tome un parámetro del tipoconst int &
, solo use unint
aquí.static
da un error `ISO C ++ prohíbe la inicialización del miembro 'kConst' ... haciendo que 'kConst' sea estático. 'std::min( some_val, kConst)
, ya questd::min<T>
tiene parámetros de tipoT const &
, y la implicación es que tenemos que pasar una referencia a la kConst. Descubrí que ocurría solo cuando la optimización estaba desactivada. Se corrigió usando un yeso estático.Respuestas:
Es intencional, 9.4.2 / 4 dice:
Cuando pasa el miembro de datos estáticos por referencia constante, lo "usa", 3.2 / 2:
Entonces, de hecho, lo "usa" cuando lo pasa por valor también, o en un
static_cast
. Es solo que GCC lo ha dejado libre en un caso, pero no en el otro.[Editar: gcc está aplicando las reglas de los borradores de C ++ 0x: "Una función variable o no sobrecargada cuyo nombre aparece como una expresión potencialmente evaluada se usa odr a menos que sea un objeto que satisfaga los requisitos para aparecer en una expresión (5.19) y se aplica inmediatamente la conversión de lvalor a rvalue (4.1). ". La conversión estática realiza la conversión lvalue-rvalue inmediatamente, por lo que en C ++ 0x no se "usa".]
El problema práctico con la referencia constante es que
foo
tiene derecho a tomar la dirección de su argumento y compararla, por ejemplo, con la dirección del argumento de otra llamada, almacenada en un global. Dado que un miembro de datos estáticos es un objeto único, esto significa que si llamafoo(kConst)
desde dos TU diferentes, la dirección del objeto pasado debe ser la misma en cada caso. AFAIK GCC no puede arreglar eso a menos que el objeto esté definido en una (y solo una) TU.Bien, en este caso
foo
es una plantilla, por lo tanto, la definición es visible en todas las TU, por lo que quizás el compilador podría, en teoría, descartar el riesgo de que haga algo con la dirección. Pero, en general, no debería tomar direcciones o referencias a objetos inexistentes ;-)fuente
template <int N> int intvalue() { return N; }
. Entonces conintvalue<kConst>
,kConst
solo aparece en un contexto que requiere una expresión constante integral, por lo que no se usa. Pero la función devuelve un temporal con el mismo valor quekConst
, y que se puede vincular a una referencia constante. Sin embargo, no estoy seguro de que haya una forma más sencilla de hacer cumplir de forma portátil quekConst
no se utiliza.r = s ? kConst1 : kConst2
) con gcc 4.7. Lo resolví usando un archivo realif
. De todos modos gracias por la respuesta!Si está escribiendo una variable const estática con un inicializador dentro de la declaración de clase, es como si hubiera escrito
class Bar { enum { kConst = 1 }; }
y GCC lo tratará de la misma manera, lo que significa que no tiene dirección.
El código correcto debe ser
class Bar { static const int kConst; } const int Bar::kConst = 1;
fuente
Este es un caso realmente válido. Especialmente porque foo podría ser una función de STL como std :: count que toma una constante T & como su tercer argumento.
Pasé mucho tiempo tratando de entender por qué el enlazador tenía problemas con un código tan básico.
El mensaje de error
nos dice que el enlazador no puede encontrar un símbolo.
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 U Bar::kConst
Podemos ver en la 'U' que Bar :: kConst no está definido. Por tanto, cuando el enlazador intenta hacer su trabajo, tiene que encontrar el símbolo. Pero solo declara kConst y no lo define.
La solución en C ++ también es definirlo de la siguiente manera:
template <typename T> void foo(const T & a) { /* code */ } class Bar { public: static const int kConst = 1; void func() { foo(kConst); // This is the important line } }; const int Bar::kConst; // Definition <--FIX int main() { Bar b; b.func(); }
Luego, puede ver que el compilador colocará la definición en el archivo objeto generado:
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 R Bar::kConst
Ahora, puede ver la 'R' que dice que está definida en la sección de datos.
fuente
>nm -C main.o | grep kConst
solo me da una línea0000000000400644 R Bar::kConst
.nm -C lib.a
le daConstants.o: 0000000000000000 R Bar::kConst
ymain_file.o: U Bar::kConst ...
.g ++ versión 4.3.4 acepta este código (ver este enlace ). Pero la versión 4.4.0 de g ++ lo rechaza.
fuente
Creo que este artefacto de C ++ significa que cada vez que
Bar::kConst
se hace referencia, se usa su valor literal en su lugar.Esto significa que en la práctica no hay ninguna variable a la que hacer referencia.
Puede que tenga que hacer esto:
void func() { int k = kConst; foo(k); }
fuente
foo(static_cast<int>(kConst));
, ¿verdad?También puede reemplazarlo por una función miembro constexpr:
class Bar { static constexpr int kConst() { return 1; }; };
fuente
Truco simple: use
+
antes dekConst
pasar la función. Esto evitará que se tome una referencia a la constante, y de esta manera el código no generará una solicitud de enlazador al objeto constante, sino que continuará con el valor de la constante en tiempo del compilador.fuente
static const
valor que se inicializa en la declaración. Esto siempre daría lugar a un error del vinculador, y cuando la misma constante también se declara por separado en un archivo de objeto, esto también sería un error. El compilador también es plenamente consciente de la situación.static_cast<decltype(kConst)>(kConst)
.foo()
pregunta de la pregunta original necesitará la dirección, y no hay ningún mecanismo para tratarla como una copia temporal de toda la matriz.Experimenté el mismo problema mencionado por Cloderic (const estática en un operador ternario :)
r = s ? kConst1 : kConst2
, pero solo se quejó después de desactivar la optimización del compilador (-O0 en lugar de -Os). Ocurrió en gcc-none-eabi 4.8.5.fuente