Undefined ссылка на static const int
Сегодня я столкнулся с интересной проблемой. Рассмотрим этот простой пример:
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();
}
При компиляции я получаю сообщение об ошибке:
Undefined reference to 'Bar::kConst'
Теперь я уверен, что это связано с тем, что static const int
не определен нигде, что является преднамеренным, потому что, согласно моему пониманию, компилятор должен иметь возможность выполнять замену во время компиляции и не нуждаться в определении. Однако, поскольку функция принимает параметр const int &
, кажется, что она не делает замену и вместо этого предпочитает ссылку. Я могу решить эту проблему, внеся следующие изменения:
foo(static_cast<int>(kConst));
Я считаю, что теперь это вынуждает компилятор сделать временный int, а затем передать ссылку на это, что он может успешно выполнить во время компиляции.
Мне было интересно, было ли это намеренно, или я ожидаю слишком многого от gcc, чтобы иметь возможность справиться с этим делом? Или это почему-то я не должен делать по какой-то причине?
Ответы
Ответ 1
Это преднамеренное, 9.4.2/4 говорит:
Если статический член данных имеет тип const const или const, его объявление в классе определение может указывать константный инициализатор, который должен быть интегральное постоянное выражение (5.19) В в этом случае член может появиться в интегральные постоянные выражения. член должен быть определен в область пространства имен, если она используется в Программа
Когда вы передаете статический член данных по ссылке const, вы "используете" его, 3.2/2:
Выражение потенциально оценивается если оно не появляется, когда интеграл требуется постоянное выражение (см. 5.19), является операндом оператора size (5.3.3) или является операндом оператор типа и выражение не обозначает lvalue полиморфный тип класса (5.2.8). объект или неперегруженная функция используется, если его имя отображается в потенциально оцениваемое выражение.
Таким образом, вы "используете" его, когда вы передаете его по значению, или в static_cast
. Это просто, что GCC отпустил вас от крючка в одном случае, но не в другом.
[Edit: gcc применяет правила из С++ 0x drafts: "Переменная или неперегруженная функция, имя которой отображается как потенциально оцениваемое выражение, используется odr, если это не объект, который удовлетворяет требованиям для появления в постоянном выражении (5.19) и немедленно применяется преобразование lvalue-to-r (4.1).". Статическое литье выполняет немедленное преобразование lvalue-rvalue, поэтому в С++ 0x оно не используется.]
Практическая проблема с ссылкой на const заключается в том, что foo
находится в пределах своих прав, чтобы взять адрес своего аргумента и сравнить его, например, с адресом аргумента из другого вызова, хранящегося в глобальном. Поскольку статический член данных является уникальным объектом, это означает, что если вы вызываете foo(kConst)
из двух разных ТУ, тогда адрес переданного объекта должен быть одинаковым в каждом случае. AFAIK GCC не может организовать это, если объект не определен в одном (и только одном) TU.
ОК, поэтому в этом случае foo
является шаблоном, поэтому определение видимо во всех TU, поэтому, возможно, компилятор теоретически может исключить риск того, что он что-то сделает с адресом. Но в целом вы, конечно, не должны принимать адреса или ссылки на несуществующие объекты; -)
Ответ 2
Если вы пишете переменную static const с инициализатором внутри объявления класса, это так же, как если бы вы написали
class Bar
{
enum { kConst = 1 };
}
и GCC будет обрабатывать его таким же образом, что означает, что у него нет адреса.
Правильный код должен быть
class Bar
{
static const int kConst;
}
const int Bar::kConst = 1;
Ответ 3
Это действительно действительный случай. Тем более, что foo может быть функцией из STL, например std:: count, которая в качестве третьего аргумента принимает const T &.
Я потратил много времени, пытаясь понять, почему у компоновщика возникли проблемы с таким базовым кодом.
Сообщение об ошибке
Undefined ссылка на "Bar:: kConst"
сообщает нам, что компоновщик не может найти символ.
$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst
Из "U" видно, что Bar:: kConst - undefined. Следовательно, когда компоновщик пытается выполнить свою работу, он должен найти символ. Но вы объявляете kConst и не определяете его.
Решение в С++ также должно определять его следующим образом:
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();
}
Затем вы можете увидеть, что компилятор поместит определение в сгенерированный объектный файл:
$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst
Теперь вы можете видеть, что "R" говорит, что он определен в разделе данных.
Ответ 4
g++ версия 4.3.4 принимает этот код (см. эта ссылка). Но g++ версия 4.4.0 отклоняет его.
Ответ 5
Я думаю, что этот артефакт С++ означает, что в любое время, на которое ссылается Bar::kConst
, вместо него используется его буквальное значение.
Это означает, что на практике нет переменной для создания контрольной точки.
Возможно, вам придется это сделать:
void func()
{
int k = kConst;
foo(k);
}
Ответ 6
Простой трюк: используйте +
, прежде чем kConst
передаст функцию. Это предотвратит получение константы от ссылки, и таким образом код не будет генерировать запрос компоновщика к постоянному объекту, но он будет продолжаться с постоянным значением времени компилятора.
Ответ 7
Вы также можете заменить его на функцию constexpr:
class Bar
{
static constexpr int kConst() { return 1; };
};