C++模板类static const变量成员作为map键给出了未定义的引用

Plu*_*luc 3 c++ templates g++ undefined-reference c++11

我有一堆类具有静态成员,这是一个枚举值.而且我在其他地方有一张地图,这个枚举是关键.现在,如果我在函数中使用模板参数来访问地图,我会得到一个未定义的引用.

为清楚起见,这是一个简化的非工作示例:

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"}};
Run Code Online (Sandbox Code Playgroud)

主要:

fun<A<1337>();
Run Code Online (Sandbox Code Playgroud)

给了我(g ++ 4.7):

undefined reference to `(anonymous namespace)::A<1337>::Type'
Run Code Online (Sandbox Code Playgroud)

不过这个:

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

编译和打印 1337

有人可以解释我这种行为吗?

Lig*_*ica 6

使用时T::Type,必须定义它:

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

template <int T>
const int A<T>::Type;
Run Code Online (Sandbox Code Playgroud)

是的,即使您在内部提供了初始化内容A<T>!

您可能没有意识到这一点的原因与您在第二种情况下没有遇到相同问题的原因相同 - 由于直接左值到右值转换,标准允许编译器优化需求Type在运行时引用,而不是在编译时选择值.然后链接器不需要搜索定义,也不会出现错误.


[C++11: 9.4.2/2]: 静态数据成员在其类定义中的声明不是定义,除了cv-qualified void之外可能是不完整的类型.静态数据成员的定义应出现在包含成员类定义的命名空间范围内.在命名空间作用域的定义中,静态数据成员的名称应使用::运算符通过其类名限定.静态数据成员定义中的初始化表达式在其类的范围内(3.3.7).[..]

[C++11: 9.4.2/3]: 如果非易失性const static数据成员是整数类型或枚举类型,则其在类定义中的声明可以指定大括号或等于初始化程序,其中作为赋值表达式的每个initializer子句都是常量表达式(5.19).static data可以使用说明constexpr符在类定义中声明文字类型的成员; 如果是这样,它的声明应指定一个大括号或等于初始化器,其中作为赋值表达式的每个initializer子句都是一个常量表达式.[注意:在这两种情况下,成员可能会出现在常量表达式中.-end note] 如果在程序中使用odr-used(3.2)并且命名空间作用域定义不包含初始化程序,则仍应在命名空间作用域中定义该成员.

[C++11: 3.2/2]: [..]一个名称显示为潜在评估表达式的变量是odr-used,除非它是一个满足出现在常量表达式(5.19)中的要求的对象,并且lvalue-to-rvalue转换(4.1)是立即的应用.[..]