变量模板的编译器问题

compiler problems with variable template

以下代码(摘自Wikipedia) defines the variable templatepi<>

template<typename T=double>
constexpr T pi = T(3.14159265358979323846264338328);
template<>
constexpr const char* pi<const char*> = "π";

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

no previous extern declaration for non-static variable 'pi<const char *>'

declare 'static' if the variable is not intended to be used outside of this translation unit

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

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

template<>
static constexpr const char* pi<const char*> = "π";

但是现在 gcc (9.3.0) 不高兴了,给出一个 error 指向 static 关键字:

error: explicit template specialization cannot have a storage class

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

来自(旧版本)Clang 的警告在一定程度上具有误导性,但确实指出了您最终遇到的真实问题链接器。该警告描述了全局变量应该

的良好经验法则
  1. 在 header 中与 extern 一起出现,然后在源文件中没有出现,或者
  2. 在源文件中与 static 一起出现(避免与任何其他符号冲突)。

后一种选择不适用于显式特化:因为链接作为一个 whole 适用于模板(标准说它属于 name 的模板,即使它不能很好地用于重载函数也是令人回味的),你不能只做一个专业化 static 并且 Clang 是 不正确的 接受它。 (MSVC 也错误地接受了这一点。)进行“file-local 特化”的唯一方法是使用本地类型、模板或 object 的模板参数。您当然可以使整个变量模板与 static 或未命名的命名空间具有内部链接。

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

// pi.hh (excerpt)
template<typename T=double>
constexpr T pi = T(3.14159265358979323846264338328);
template<>
extern constexpr const char* pi<const char*>;

// pi.cc
#include"pi.hh"
template<>
constexpr const char* pi<const char*> = "π";

(因为主模板是一个模板,所以它 在 header 文件中定义的。)

如评论中所述,C++17允许内联变量;您的显式专业化再次表现得像一个普通的全局变量,如果需要,可以在 header 中用 inline 定义。