[英]Template tricks with const char* as a non-type parameter
我非常清楚直接传递const char*
作为模板非类型参数是错误的,因为在两个不同的转换单元中定义的两个相同的字符串文字可能具有不同的地址(尽管大多数情况下编译器使用相同的地址) 。 可以使用一个技巧,请参阅下面的代码:
#include <iostream>
template<const char* msg>
void display()
{
std::cout << msg << std::endl;
}
// need to have external linkage
// so that there are no multiple definitions
extern const char str1[] = "Test 1"; // (1)
// Why constexpr is enough? Does it have external linkage?
constexpr char str2[] = "Test 2"; // (2)
// Why doesn't this work?
extern const char* str3 = "Test 3"; // (3) doesn't work
// using C_PTR_CHAR = const char* const; // (4) doesn't work either
extern constexpr C_PTR_CHAR str4 = "Test 4";
int main()
{
display<str1>(); // (1')
display<str2>(); // (2')
// display<str3>(); // (3') doesn't compile
//display<str4>(); // (4') doesn't compile
}
基本上在(1)中我们声明并定义一个带有外部链接的数组,然后可以将其用作(1')中的模板参数。 我非常了解这一点。 但是,我不明白:
为什么constexpr
版本(2)有效? constexpr
有外部联系吗? 如果不是,则在不同的翻译单元中定义相同的字符串文字可能导致重复的模板实例化。
为什么(3)和(4)不起作用? 对我来说这似乎是完全合理的,但编译器并不这么认为:
错误:'str3'不是有效的模板参数,因为'str3'是变量,而不是变量的地址
1.简短的回答:它的工作原理与它被声明为constexpr
,因为你定义了一个具有静态存储持续时间的对象(不是字符串文字 - 它存储了一个内容的副本),并且它的地址是一个常量表达。 关于链接, str2
有内部链接,但没关系 - 它的地址可以用作非类型模板参数。
答案很长:
在C ++ 11和14中,[14.3.2p1]说明如下:
模板参数的用于非类型的,非模板的模板参数应是以下之一:
[...]
- 一个常量表达式(5.19),用于指定具有静态存储持续时间和外部或内部链接的完整对象的地址,或具有外部或内部链接的函数,包括函数模板和函数模板ID,但不包括非静态类成员,表示(忽略括号)作为
&
ID表达 ,其中的ID-expression是对象或函数的名称,除了&
如果名称是指功能或阵列,并且如果相应的模板的参数将被省略,可以省略是一个参考;[...]
因此,您可以使用具有静态存储持续时间的对象的地址,但该对象必须通过具有链接(内部或外部)的名称来标识,并且您表达该地址的方式受到限制。 (字符串文字不是名称,也没有链接。)
简而言之,甚至char str1[] = "Test 1";
作品。 static char str1[] = "Test 1";
很好; GCC 5.1.0拒绝它,但我认为这是一个错误; Clang 3.6.0接受了它。
关于str2
的链接,C ++ 11和14 [3.5p3]说:
具有命名空间作用域(3.3.6)的名称具有内部链接(如果它的名称)
[...]
- 一个非易失性变量,显式声明为
const
或constexpr
,既未显式声明为extern
也未声明为具有外部链接;[...]
由于DR 1686 ,N4431略有改变:
- 非易失性const限定类型的变量,既未显式声明为
extern
也未声明为具有外部链接;
反映constexpr
意味着对象的const限定这一事实。
2.简答:对于C ++ 11和14,见上文; 对于草案C ++ 1z, str3
不是一个常量表达式,因为指针本身不是constexpr
,它也是字符串文字的地址。 str4
是常量,但仍然是字符串文字的地址。
答案很长:
在当前的工作草案N4431中,放宽了对非类型模板参数的约束。 [14.3.2p1]现在说:
模板参数的用于非类型模板参数应为模板参数的类型的一个转换后的常量表达式(5.20)。 对于引用或指针类型的非类型模板参数 ,常量表达式的值不应引用(或者对于指针类型,不应该是地址):
- 子对象(1.8),
- 一个临时物体(12.2),
- 字符串文字(2.13.5),
typeid
表达式的结果(5.2.8),或- 预定义的
__func__
变量(8.4.1)。
这些都是限制。 转换后的常量表达式部分非常重要; 完整定义很长 ,但与我们的案例相关的一部分是具有静态存储持续时间的对象的地址就是这样的表达式。
同样相关的是,根据[5.20p2.7],应用了左值到右值的转换
非易失性glvalue,引用
constexpr
定义的非易失性对象,或引用此类对象的非可变子对象
也满足了作为常数表达的条件。 这允许我们使用一些constexpr
指针变量作为非类型模板参数。 (请注意,仅仅声明变量const
是不够的,因为它可以使用非常量表达式进行初始化。)
所以,像constexpr const char* str3 = str1;
很好。 它在C ++ 1z模式下被Clang 3.6.0接受(在C ++ 14模式下被拒绝); GCC 5.1.0仍然拒绝它 - 它看起来还没有实现更新的规则。
但是,字符串文字有什么问题? 这是问题(N4431 [2.13.5p16]):
评估字符串文字会产生具有静态存储持续时间的字符串文字对象,从上面指定的给定字符初始化。 是否所有字符串文字都是不同的(即,存储在非重叠对象中)以及是否对字符串文字的连续评估产生相同或不同的对象是未指定的。
允许实现使用字符串文字做很多事情:混合,匹配,使它们重叠(完全或部分),从同一个翻译单元制作7个副本 - 无论如何。 这使得字符串文字的地址不可用作非类型模板参数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.