使用 const char* 作为非类型参数的模板技巧
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 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')中将其用作模板参数。我非常理解这一点。但是,我不明白:
为什么 constexpr
版本 (2) 有效? constexpr
是否有外部链接?如果不是,那么在不同的翻译单元中定义相同的字符串文字可能会导致重复的模板实例化。
为什么 (3) 和 (4) 不起作用?这对我来说似乎完全合理,但编译器不这么认为:
error: 'str3' is not a valid template argument because 'str3' is a variable, not the address of a variable
1. 简短回答:无论它是否被声明,它都有效 constexpr
,因为您正在定义一个具有静态存储持续时间的对象(不是字符串literal - 它存储 one) 内容的副本,它的地址是一个常量表达式。关于链接,str2
有内部链接,但这很好 - 它的地址可以用作非类型模板参数。
长答案:
在 C++11 和 14 中,[14.3.2p1] 表示如下:
A template-argument for a non-type, non-template template-parameter
shall be one of:
[...]
- a constant expression (5.19) that designates the address of a complete object with static storage duration and external or internal
linkage or a function with external or internal linkage, including
function templates and function template-ids but excluding non-static
class members, expressed (ignoring parentheses) as
&
id-expression,
where the id-expression is the name of an object or function, except
that the &
may be omitted if the name refers to a function or array
and shall be omitted if the corresponding template-parameter is a
reference;
[...]
因此,您可以使用具有静态存储持续时间的对象地址,但该对象必须由具有链接(内部或外部)的名称标识,并且您表达该地址的方式受到限制。 (字符串文字不是名称,也没有链接。)
简而言之,即使 char str1[] = "Test 1";
也可以。 static char str1[] = "Test 1";
也可以; GCC 5.1.0 拒绝它,但我认为这是一个错误; Clang 3.6.0 接受它。
关于 str2
的链接,C++11 和 14 [3.5p3] 说:
A name having namespace scope (3.3.6) has internal linkage if
it is the name of
[...]
- a non-volatile variable that is explicitly declared
const
or constexpr
and neither explicitly declared extern
nor previously
declared to have external linkage;
[...]
N4431 由于 DR 1686,将其略微更改为:
- a variable of non-volatile const-qualified type that is neither explicitly declared
extern
nor previously declared to have external
linkage;
反映出 constexpr
意味着对象的 const 限定。
2. 简短回答:对于 C++11 和 14,请参见上文;对于 C++1z 草案,str3
不是常量表达式,因为指针本身不是 constexpr
,它也是字符串文字的地址。 str4
是常量,但仍然是字符串文字的地址。
长答案:
在当前的工作草案 N4431 中,放宽了对非类型模板参数的限制。 [14.3.2p1] 现在说:
A template-argument for a non-type template-parameter shall be a
converted constant expression (5.20) of the type of the
template-parameter. For a non-type template-parameter of reference or
pointer type, the value of the constant expression shall not refer to
(or for a pointer type, shall not be the address of):
- a subobject (1.8),
- a temporary object (12.2),
- a string literal (2.13.5),
- the result of a
typeid
expression (5.2.8), or
- a predefined
__func__
variable (8.4.1).
这些都是限制。 converted constant expression 部分非常重要;完整的定义是 long,但与我们的案例相关的部分是具有静态存储持续时间的对象的地址是这样的表达式。
同样相关的是,根据 [5.20p2.7],左值到右值的转换 应用于
a non-volatile glvalue that refers to a non-volatile object defined
with constexpr
, or that refers to a non-mutable sub-object of such an
object
也满足常量表达式的条件。这允许我们使用一些 constexpr
指针变量作为非类型模板参数。 (请注意,简单地声明一个变量 const
是不够的,因为它可以用非常量表达式初始化。)
所以,constexpr const char* str3 = str1;
之类的就可以了。它在 C++1z 模式下被 Clang 3.6.0 接受(在 C++14 模式下被拒绝); GCC 5.1.0 仍然拒绝它 - 看起来它还没有实现更新的规则。
不过,字符串文字有什么问题?这是问题所在 (N4431 [2.13.5p16]):
Evaluating a string-literal results in a string literal object with
static storage duration, initialized from the given characters as
specified above. Whether all string literals are distinct (that is,
are stored in nonoverlapping objects) and whether successive
evaluations of a string-literal yield the same or a different object
is unspecified.
允许一个实现对字符串文字做很多事情:混合、匹配、使它们重叠(完全或部分)、从同一个翻译单元制作 7 个副本 - 随便什么。这使得字符串文字的地址无法用作非类型模板参数。
我非常清楚直接传递 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')中将其用作模板参数。我非常理解这一点。但是,我不明白:
为什么
constexpr
版本 (2) 有效?constexpr
是否有外部链接?如果不是,那么在不同的翻译单元中定义相同的字符串文字可能会导致重复的模板实例化。为什么 (3) 和 (4) 不起作用?这对我来说似乎完全合理,但编译器不这么认为:
error: 'str3' is not a valid template argument because 'str3' is a variable, not the address of a variable
1. 简短回答:无论它是否被声明,它都有效 constexpr
,因为您正在定义一个具有静态存储持续时间的对象(不是字符串literal - 它存储 one) 内容的副本,它的地址是一个常量表达式。关于链接,str2
有内部链接,但这很好 - 它的地址可以用作非类型模板参数。
长答案:
在 C++11 和 14 中,[14.3.2p1] 表示如下:
A template-argument for a non-type, non-template template-parameter shall be one of:
[...]
- a constant expression (5.19) that designates the address of a complete object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as
&
id-expression, where the id-expression is the name of an object or function, except that the&
may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference;[...]
因此,您可以使用具有静态存储持续时间的对象地址,但该对象必须由具有链接(内部或外部)的名称标识,并且您表达该地址的方式受到限制。 (字符串文字不是名称,也没有链接。)
简而言之,即使 char str1[] = "Test 1";
也可以。 static char str1[] = "Test 1";
也可以; GCC 5.1.0 拒绝它,但我认为这是一个错误; Clang 3.6.0 接受它。
关于 str2
的链接,C++11 和 14 [3.5p3] 说:
A name having namespace scope (3.3.6) has internal linkage if it is the name of
[...]
- a non-volatile variable that is explicitly declared
const
orconstexpr
and neither explicitly declaredextern
nor previously declared to have external linkage;[...]
N4431 由于 DR 1686,将其略微更改为:
- a variable of non-volatile const-qualified type that is neither explicitly declared
extern
nor previously declared to have external linkage;
反映出 constexpr
意味着对象的 const 限定。
2. 简短回答:对于 C++11 和 14,请参见上文;对于 C++1z 草案,str3
不是常量表达式,因为指针本身不是 constexpr
,它也是字符串文字的地址。 str4
是常量,但仍然是字符串文字的地址。
长答案:
在当前的工作草案 N4431 中,放宽了对非类型模板参数的限制。 [14.3.2p1] 现在说:
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter. For a non-type template-parameter of reference or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):
- a subobject (1.8),
- a temporary object (12.2),
- a string literal (2.13.5),
- the result of a
typeid
expression (5.2.8), or- a predefined
__func__
variable (8.4.1).
这些都是限制。 converted constant expression 部分非常重要;完整的定义是 long,但与我们的案例相关的部分是具有静态存储持续时间的对象的地址是这样的表达式。
同样相关的是,根据 [5.20p2.7],左值到右值的转换 应用于
a non-volatile glvalue that refers to a non-volatile object defined with
constexpr
, or that refers to a non-mutable sub-object of such an object
也满足常量表达式的条件。这允许我们使用一些 constexpr
指针变量作为非类型模板参数。 (请注意,简单地声明一个变量 const
是不够的,因为它可以用非常量表达式初始化。)
所以,constexpr const char* str3 = str1;
之类的就可以了。它在 C++1z 模式下被 Clang 3.6.0 接受(在 C++14 模式下被拒绝); GCC 5.1.0 仍然拒绝它 - 看起来它还没有实现更新的规则。
不过,字符串文字有什么问题?这是问题所在 (N4431 [2.13.5p16]):
Evaluating a string-literal results in a string literal object with static storage duration, initialized from the given characters as specified above. Whether all string literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified.
允许一个实现对字符串文字做很多事情:混合、匹配、使它们重叠(完全或部分)、从同一个翻译单元制作 7 个副本 - 随便什么。这使得字符串文字的地址无法用作非类型模板参数。