我真的不明白为什么下面的代码无法编译:
template<const char*>
struct Foo{};
constexpr const char s1[] = "test1";
constexpr const char* const s2 = "test2";
int main()
{
Foo<s1> foo1; // ok
// Foo<s2> foo2; // doesn't compile
}
取消注释main()
中的最后一行会使g ++和clang ++发出错误
error: 's2' is not a valid template argument because 's2' is a variable, not the address of a variable
和
error: non-type template argument for template parameter of pointer type 'const char *' must have its address taken
分别
我的问题是:
s1
实例化正常而s2
没有?答案 0 :(得分:7)
在上面的评论中,vsoftco补充道:
看起来非常奇怪,afaik字符串文字不是临时文件,而是存储在程序的整个持续时间内,所以它们的地址肯定是编译时间常数(或者至少是我所相信的那样)
那是真的。但是,标准没有指定字符串文字是否具有唯一地址。
某些链接器合并或重复删除字符串文字。我曾在"ello" == "hello"+1
实际评估为true
的系统上工作过。其他链接器非常愚蠢,foo.cc中的"hello"
与bar.cc中的"hello"
具有不同的地址。哎呀,一些小C编译器是如此愚蠢,"hello"
可以在同一个翻译单元中有两个不同的地址!
对于这样一个愚蠢的链接器(或编译器),Foo<"hello">
应该导致一个或两个实例吗?那是......
const char *sa = "hello world";
const char *sb = "hello world";
assert(sa != sb); // this assertion is permitted to succeed
template<char*> struct F {};
F<"hello world"> fa;
F<"hello world"> fb;
assert(!is_same<decltype(fa), decltype(fb)>::value);
// should we permit this assertion to succeed also?
委员会令人钦佩地拒绝通过简单地禁止这种构造来打开那种蠕虫病毒。
现在,可以想象(对我来说,目前)将来某个时候委员会可以强制要求所有字符串文字都通过与当前实现的实现相同的机制进行重复数据删除inline
和template
函数。也就是说,我们可以想象一个转换的源级转换
const char *sc = "yoo hoo";
到
inline auto& __stringlit_yoo_x20hoo() {
static const char x[] = "yoo hoo";
return x;
}
const char *sc = __stringlit_yoo_x20hoo();
然后程序中的任何地方都只有__stringlit_yoo_x20hoo
的单个实例(并且该函数只有一个实例的静态数组x
),因此{{1毫无疑问。实现必须明确地命名 - 但是,一旦你已经致力于命名诸如F<"yoo hoo">
和F<1+1>
之类的东西,这就是一个简单的问题(C ++编译器一直在做。)
...但是那时你会仍然与那些非常聪明的连接器(比如我工作的连接器)有问题
F<FruitType,ORANGE>
答案 1 :(得分:6)
1:
来自[temp.arg.nontype]
1非类型模板参数的模板参数应为模板参数类型的转换常量表达式(5.20)。对于引用或指针类型的非类型模板参数,常量表达式的值不应引用(或指针类型,不应是的地址):
[...]
(1.3) - 字符串文字(2.13.5),
s2
保存字符串文字的地址,因此不能在此处用作参数。另一方面,s1
是一个char
数组,已使用字符串文字初始化,但s1
的值(转换为{{1}时}})不指向初始化中使用的字符串文字。
2:
函数指针也许?我仍然不能说我曾经使用指针作为非类型参数。
答案 2 :(得分:6)
最近有相关标准文本发生了变化,但在两个版本的标准中都不接受该代码。
N4140 [temp.arg.nontype] / p1:
1 template-argument ,用于非类型的非模板 template-parameter应为以下之一:
- 表示整数或枚举类型的非类型模板参数,转换后的常量表达式(5.19)的类型 模板参数;或
- 非类型模板参数的名称;或
- 一个常量表达式(5.19),用于指定具有静态存储持续时间和外部或内部的完整对象的地址 连接或具有外部或内部联系的功能,包括 功能模板和功能 template-ids 但不包括在内 非静态类成员,表示(忽略括号)为
& id-expression
, 其中 id-expression 是对象的名称或 函数,但如果名称引用a,则&
可以省略 函数或数组,如果相应则应省略 template-parameter 是一个参考;或- 一个求值为空指针值的常量表达式(4.10);或
- 一个常量表达式,其值为null成员指针值(4.11);或
- 指向成员的指针,如5.3.1所述;或
- 类型
std::nullptr_t
的常量表达式。
N4296 [temp.arg.nontype] / p1:
非类型模板参数的 template-argument 应为 转换常数表达式(5.20)的类型 模板参数。对于引用或指针类型的非类型模板参数,常量表达式的值不应引用 to(或指针类型,不应是地址):
- 子对象(1.8),
- 临时对象(12.2),
- 字符串文字(2.13.5),
- 的结果
typeid
表达式(5.2.8)或- 预定义的
__func__
变量(8.4.1)。
N4140版本是目前由编译器实现的版本。 N4296版本稍微宽松一些,但在两种情况下,字符串文字的地址都不是可接受的模板参数。
可能原因之一是模板参数必须被修改,并且以一种理智的方式修改字符串文字,如果不是不可能的话,将很难在多个翻译单元中工作。