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

Template tricks with const char* as a non-type parameter

本文关键字:类型参数 char const 使用      更新时间:2023-10-16

我很清楚,直接传递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 is constexpr 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')中的模板参数。我非常理解这一点。然而,我不明白:

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

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

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

1。简单回答:无论它被声明为constexpr,它都能工作,因为您定义的对象具有静态存储持续时间(不是字符串文字-它存储一个对象的内容的副本),并且它的地址是一个常量表达式。关于链接,str2有内部链接,但这很好——它的地址可以用作非类型模板参数。

长答案:

在C++11和14中,[114.3.2p1]表示如下:

非类型、非模板的模板参数应为以下参数之一:
[…]

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

[…]

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

简而言之,即使是char str1[] = "Test 1";也能起作用。CCD_ 9也很好;GCC 5.1.0拒绝了它,但我认为这是一个错误;Clang 3.6.0接受。


关于str2的链接,C++11和14[3.5p3]说:

如果这是
[…]的名称

  • 显式声明为constconstexpr且既没有显式声明extern也没有先前声明的非易失性变量声明具有外部链接

[…]

N4431由于DR 1686而将其略微更改为:

  • 非易失性常量限定类型的变量,既没有显式声明为extern,也没有以前声明为具有外部联动

反映了constexpr意味着对象的常量限定这一事实。


2。简短回答:对于C++11和14,请参阅上文;对于草案C++1z,str3不是一个常量表达式,因为指针本身不是constexpr,它也是字符串文字的地址。str4是常量,但仍然是字符串文字的地址。

长答案:

在目前的工作草案N4431中,对非类型模板参数的限制已经放宽。[114.3.2p1]现在说:

非类型模板参数的模板自变量应为类型的转换常量表达式(5.20)模板参数。对于引用的非类型模板参数或指针类型,常量表达式的值不应引用(或对于指针类型,不应为的地址):

  • 子对象(1.8)
  • 临时对象(12.2)
  • 字符串文字(2.13.5)
  • typeid表达的结果(5.2.8),或
  • 预定义的CCD_ 20变量(8.4.1)

这些都是限制。转换的常量表达式部分非常重要;完整的定义是long,但与我们的情况相关的一部分是,具有静态存储持续时间的对象的地址就是这样一个表达式。

同样相关的是,根据[5.2p2.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个副本——不管怎样。这使得字符串文字的地址不能用作非类型模板参数。