[英]C++ template class static const variable member as map key gives undefined reference
我有一堆类具有静态成员,这是一个枚举值。 而且我在其他地方有一张地图,这个枚举是关键。 现在,如果我在函数中使用模板参数来访问地图,我会得到一个未定义的引用。
为清楚起见,这是一个简化的非工作示例:
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
有人可以解释我这种行为吗?
使用T::Type
,必须定义它:
template<int T>
struct A
{
static const int Type = T;
}
template <int T>
const int A<T>::Type;
是的,即使你在A<T>
提供了内联的初始化!
您可能没有意识到这一点的原因与您在第二种情况下没有遇到相同问题的原因相同 - 由于直接左值到右值转换,标准允许编译器优化需求在运行时引用Type
,而不是在编译时选择值。 然后链接器不需要搜索定义,也不会出现错误。
[C++11: 9.4.2/2]:
静态数据成员在其类定义中的声明不是定义 ,除了cv-qualified void之外可能是不完整的类型。 静态数据成员的定义应出现在包含成员类定义的命名空间范围内 。 在命名空间作用域的定义中,静态数据成员的名称应使用::运算符通过其类名限定。 静态数据成员定义中的初始化表达式在其类的范围内(3.3.7)。 [..]
[C++11: 9.4.2/3]:
如果一个非易失性const static
数据成员是整数或枚举类型,它在类定义中的声明可以指定一个大括号 - 或者相等 - 初始化器 ,其中每个初始化器 -赋值表达式是一个常量表达式(5.19)。 可以使用constexpr
说明符在类定义中声明文字类型的static data
成员; 如果是这样,它的声明应指定一个大括号或等于初始化器 ,其中作为赋值表达式的每个initializer子句都是一个常量表达式。 [注意:在这两种情况下,成员可能会出现在常量表达式中。 -end note] 如果在程序中使用odr-used(3.2)并且命名空间作用域定义不包含初始化程序,则仍应在命名空间作用域中定义该成员。
[C++11: 3.2/2]:
[..]一个名称显示为潜在评估表达式的变量是odr-used,除非它是一个满足出现在常量表达式(5.19)和立即应用左值到右值的转换(4.1)。 [..]
这是因为std::map::operator[]
通过引用获取其参数,这使得您的变量使用了odr (参见C ++ 11标准的第3.2 / 3段)。
简而言之,整个事情归结为这样一个事实:编译器在需要绑定对象的引用时需要知道对象的地址 ,这使得它无法像纯值那样处理该对象并执行内联。
在这种情况下,您需要在全局命名空间范围内提供静态数据成员的定义 ,以便编译器知道该对象占用的存储区域(即其地址是什么):
template<int T>
const int A::Type;
根据C ++ 11标准的第9.4.2 / 3段:
如果非易失性
const
静态数据成员是整数类型或枚举类型,则其在类定义中的声明可以指定一个大括号或大小为初始化器 ,其中作为赋值表达式的每个initializer子句都是一个常量表达式(5.19) )。 [...] 如果程序中使用了odr-used(3.2),并且命名空间作用域定义不包含初始化程序,则仍应在名称空间作用域中定义该成员 。
另一方面,在程序的第一个版本中,您只使用静态数据成员的值 ,这意味着Type
不是使用odr,并且不需要在命名空间范围内定义。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.