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
Может кто-нибудь объяснить мне это поведение?
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