使用const char*作为非类型参数的模板技巧

vso*_*tco 21 c++ templates constexpr c++11

我非常清楚直接传递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
}
Run Code Online (Sandbox Code Playgroud)

基本上在(1)中我们声明并定义一个具有外部链接的数组,然后可以将其用作(1')中的模板参数.我非常了解这一点.但是,我不明白:

  1. 为什么constexpr版本(2)有效?不要constexpr有外部链接?如果不是,则在不同的翻译单元中定义相同的字符串文字可能导致重复的模板实例化.

  2. 为什么(3)和(4)不起作用?对我来说这似乎是完全合理的,但编译器并不这么认为:

    错误:'str3'不是有效的模板参数,因为'str3'是变量,而不是变量的地址

bog*_*dan 14

1.简答:无论是否声明它都有效constexpr,因为你定义了一个具有静态存储持续时间的对象(不是字符串文字 - 它存储了一个内容的副本),并且它的地址是一个常量表达式.关于链接,str2有内部链接,但没关系 - 它的地址可以用作非类型模板参数.

答案很长:

在C++ 11和14中,[14.3.2p1]说明如下:

一个模板参数对非A型,非模板的模板参数 应是一个:
[...]

  • 一个常量表达式(5.19),用于指定具有静态存储持续时间和外部或内部链接的完整对象的地址,或具有外部或内部链接的函数,包括函数模板和函数模板ID,但不包括非静态类成员,表示(忽略括号)作为& id-expression,其中id-expression是对象或函数的名称,除非&如果名称引用函数或数组可以省略,如果相应的模板参数是a ,则应省略参考;

[...]

因此,您可以使用具有静态存储持续时间的对象的地址,但该对象必须通过具有链接(内部或外部)的名称来标识,并且您表达该地址的方式受到限制.(字符串文字不是名称,也没有链接.)

简而言之,甚至是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)的名称具有内部链接,如果它是
[...] 的名称

  • 一个非易失性变量,显式声明constconstexpr既未显式声明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个副本 - 无论如何.这使得字符串文字的地址不可用作非类型模板参数.