变量模板的编译器问题

Wal*_*ter 7 c++ templates c++14

以下代码(取自维基百科)定义了变量模板 pi<>

template<typename T=double>
constexpr T pi = T(3.14159265358979323846264338328);
template<>
constexpr const char* pi<const char*> = "?";
Run Code Online (Sandbox Code Playgroud)

使用 clang 编译器(Apple clang 版本 12.0.0)(使用 C++14),这会触发警告(使用-Weverything):

之前没有非静态变量 'pi<const char *>' 的 extern 声明

如果变量不打算在此翻译单元之外使用,则声明“静态”

此外,由于这是在标头中定义的,因此创建了多个 'myNameSpace::pi<char const*>' 实例,导致链接器错误

因此,按照建议,我添加了static关键字,从而使警告静音:

template<>
static constexpr const char* pi<const char*> = "?";
Run Code Online (Sandbox Code Playgroud)

但是现在gcc(9.3.0)不爽,报错指向static关键字:

错误:显式模板特化不能有存储类

避免警告和错误的正确方法是什么?

Dav*_*ing 4

来自(这个旧版本)Clang 的警告在一定程度上具有误导性,但确实表明了您最终在链接器中遇到的真正问题。该警告描述了全局变量应该遵循的良好经验法则

\n
    \n
  1. 出现extern在标头中,然后出现在源文件中,或者
  2. \n
  3. 出现static在源文件中(避免与任何其他符号发生冲突)。
  4. \n
\n

后一种选择不适用于显式专业化:因为链接适用于整个模板标准说它与模板的名称有关,即使它不适用于重载函数,它也是令人回味的),因此您可以不能只进行一种专业化static,而 Clang接受它是不正确的。(MSVC 也错误地接受了这一点。)进行“文件本地专业化”的唯一方法是使用本地类型、模板或对象的模板参数。当然,您可以使整个变量模板与static未命名的命名空间具有内部链接。

\n

但是,前一种选择确实适用:显式专业化不是模板,因此它必须仅定义一次(在源文件中)。与任何其他全局变量一样,您可以使用extern将定义简化为声明:

\n
// pi.hh (excerpt)\ntemplate<typename T=double>\nconstexpr T pi = T(3.14159265358979323846264338328);\ntemplate<>\nextern constexpr const char* pi<const char*>;\n\n// pi.cc\n#include"pi.hh"\ntemplate<>\nconstexpr const char* pi<const char*> = "\xcf\x80";\n
Run Code Online (Sandbox Code Playgroud)\n

(由于主模板是一个模板,因此它在头文件中定义的。)

\n

正如评论中提到的,C++17 允许内联变量;您的显式专业化再次表现得像普通的全局变量,如果需要,可以inline在标头中定义。

\n