Подтвердить что ты не робот

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, чтобы иметь возможность справиться с этим делом? Или это почему-то я не должен делать по какой-то причине?

4b9b3361

Ответ 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; };
};