C++ template class static const переменный член как ключ карты дает неопределенную ссылку



У меня есть куча классов, которые имеют статический член, который является значением enum. И у меня есть карта где-то еще с этим перечислением в качестве ключа. Теперь, если я использую параметр шаблона в функции для доступа к карте, я получаю неопределенную ссылку.



Чтобы было понятно, вот упрощенный нерабочий пример:



template<int T>
struct A
{
static const int Type = T;
}

template<class T>
void fun()
{
cout << map_[T::Type] << endl;
}

map<int, string> map_{{1337, "1337"}};


Главная :



fun<A<1337>();


Дает мне (g++ 4.7):



undefined reference to `(anonymous namespace)::A<1337>::Type'


Однако это:



template<class T>
void fun()
{
auto key = T::Type;
cout << map_[key] << endl;
}


Компиляция и печать 1337



Может кто-нибудь объяснить мне это поведение?

605   2  

2 ответов:

Когда вы используете T::Type, Вы должны определить это:

template<int T>
struct A
{
   static const int Type = T;
}

template <int T>
const int A<T>::Type;

Да, хотя вы предоставили его инициализатор встроенным в A<T>!

Причина, по которой вы, возможно, не знали об этом, является той же самой причиной, по которой вы не получаете ту же проблему во втором случае - из-за немедленного преобразования lvalue в rvalue, стандарт позволяет компиляторуоптимизировать требование ссылаться на Type во время выполнения, способный вместо этого выбрать значение во время компиляции. То тогда компоновщику не нужно будет искать определение, и вы не получите ошибки.


[C++11: 9.4.2/2]: объявление статического элемента данных в его определении класса не является определением и может иметь неполный тип, отличный от CV-qualified void. определение для статического элемента данных должно появиться в области пространства имен, включающей определение класса элемента . В определении в области пространства имен имя статического объекта член данных должен быть квалифицирован его имя класса с использованием оператора::. Выражение инициализатора в определении статического элемента данных находится в области его класса (3.3.7). [..]

[C++11: 9.4.2/3]: Если энергонезависимый элемент данных const static имеет тип integral или enumeration, его объявление в определении класса может указывать инициализатор brace-or-equal в котором каждое предложение инициализатора, являющееся выражением присваивания является постоянным выражением (5.19). Член static data литерального типа может быть объявляется в определении класса со спецификатором constexpr; если это так, то его объявление должно указывать скобку-или-равный-инициализатор, в котором каждое предложение инициализатора, являющееся выражением присваивания, является постоянным выражением. [Примечание: в обоих этих случаях член может появляться в постоянных выражениях. - Конечная нота ] член все еще должен быть определен в области пространства имен, если он используется odr (3.2) в программе, и определение области пространства имен не должно содержать инициализатор.

[C++11: 3.2/2]: [..] переменная, имя которой появляется как потенциально оцениваемое выражение, используется odr, если только это не объект, удовлетворяющий требованиям для отображения в константном выражении (5.19), и преобразование lvalue-rvalue (4.1) немедленно применяется. [..]

Это потому, что std::map::operator[] принимает свой аргумент по ссылке, что делает вашу переменную odr-используемой (см. пункт 3.2 / 3 стандарта C++11).

Короче говоря, все дело сводится к тому, что компилятор должен знать адрес объекта, когда ему нужно привязать к нему ссылку, и это делает невозможным для него рассматривать этот объект просто как чистое значение и выполнять подстановку.

В этом случае вам необходимо предоставить Определение вашего статического элемента данных в области глобального пространства имен, чтобы компилятор знал, какую область хранения занимает этот объект (т. е. каков его адрес):

template<int T>
const int A::Type;

Согласно пункту 9.4.2 / 3 стандарта C++11:

Если энергонезависимый const статический элемент данных имеет интегральный или перечислительный тип, то его объявление в классе определение может указать скобку-или-равный-инициализатор , в котором каждое инициализатор-предложение, являющееся присваивание-выражение является постоянным выражением (5.19). [ ... ] член все еще должен быть определен в области пространства имен, если он используется odr (3.2) в программе и определение области пространства имен не должно содержит инициализатор .

С другой стороны, в первой версии вашей программы вы использовали только значение вашего статического элемента данных, что означает, чтоType не использовался odr, и определение в области пространства имен не требовалось.

Comments

    Ничего не найдено.