一个显着的区别在于以下代码的编译和链接:
template<typename>
struct is_pointer { };
template<typename T>
struct is_pointer<T*> {
enum { value = true };
};
void f(const bool &b) { }
int main() {
f(is_pointer<void*>::value);
}
以下内容不起作用(您会得到一个对value 的未定义引用):
template<typename>
struct is_pointer { };
template<typename T>
struct is_pointer<T*> {
static const bool value = true;
};
void f(const bool &b) { }
int main() {
f(is_pointer<void*>::value);
}
当然,除非您在某处添加以下行,否则它不起作用:
template<typename T>
const bool is_pointer<T*>::value;
那是因为[class.static.data]/3(强调我的):
如果一个非易失性非内联 const 静态数据成员是整数或枚举类型,它在类定义中的声明可以指定一个大括号或等式初始化器,其中每个初始化器子句都是一个赋值表达式是一个常量表达式 ([expr.const])。 如果该成员在程序中被 odr-used ([basic.def.odr]) 使用,则该成员仍应在命名空间范围内定义,并且命名空间范围定义不应包含初始化程序。 [...]
换句话说,static const bool value = true; 是一个声明,而不是一个定义,您不能在 odr 中使用 value。
另一方面,根据[dcl.enum/1](强调我的):
枚举是一种独特的类型具有命名常量。
如上例所示,这些命名常量可以被 const 引用。
附带说明,如果您在 C++11/14 中使用 static constexpr 数据成员,则类似情况:
template<typename T>
struct is_pointer<T*> { static constexpr bool value = true; };
这不太好,这就是我发现它们之间细微差别的方式。
我在这里找到了帮助,以便从我得到的答案中得到一些很好的提示。
对标准的引用有助于更好地解释幕后情况。
请注意,像上面这样的 static constexpr 数据成员声明也是自 C++17 以来的定义。因此,您不必再定义它,而是可以直接使用它。
正如 cmets 中提到的(感谢@Yakk 证实了这一点),我还试图解释上面提到的 命名常量 绑定到 const 引用是如何发生的。
[expr.const/3] 引入了整型常量表达式,并提到了无范围的enums,表示它被隐式转换为prvalue。
[dcl.init.ref/5] 和@ 987654325@ 完成其余的工作,因为他们对引用绑定和临时对象进行了规定。