使用const char *作为非类型参数的模板技巧 [英] Template tricks with const char* as a non-type parameter

查看:197
本文介绍了使用const char *作为非类型参数的模板技巧的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我非常清楚,直接传递 const char * 作为模板非类型参数是错误的,因为在两个不同的翻译单元中定义的两个相同的字符串文字可能具有不同的地址(虽然大多数时候编译器使用相同的地址)。有一个技巧可以使用,请参阅下面的代码:

  #include< iostream& 

template< const char * msg>
void display()
{
std :: cout< msg< std :: endl;
}

//需要外部链接
//以便没有多个定义
extern const char str1 [] =Test 1; //(1)

//为什么constexpr就够了?它有外部链接吗?
constexpr char str2 [] =Test 2; //(2)

//为什么这不工作?
extern const char * str3 =Test 3; //(3)不工作

//使用C_PTR_CHAR = const char * const; //(4)不工作
extern constexpr C_PTR_CHAR str4 =Test 4;

int main()
{
display< str1>(); //(1')
display< str2>(); //(2')
// display< str3>(); //(3')不编译
// display< str4>(); //(4')不编译
}

基本上声明和定义具有外部链接的数组,然后可以用作(1')中的模板参数。我理解得很好。但是,我不明白:


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


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


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




方案

1。简短的答案:无论是否声明 constexpr 静态存储持续时间(不是字符串字面量 - 它存储一个的内容的副本),它的地址是一个常量表达式。关于链接, str2 有内部链接,但很好 - 它的地址可以用作非类型模板参数。



长的答案:



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



模板参数应为以下类型之一: br>
[...]




  • 常量表达式(5.19),指定具有静态存储的完整对象的地址持续时间和外部或内部
    链接或具有外部或内部链接的函数,包括
    函数模板和函数 template-id ,但不包括非静态
    类成员,表示(忽略括号)为& id-expression
    其中 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)的名称具有内部链接if
它是
的名称
[...]




  • 一个非易失性变量,显式声明 const constexpr ,并且既不显式声明 extern 以前
    声明有外部链接;



[...]


由于 DR 1686 的结果,N4431略有改变, to:



  • 未明确声明的非挥发性const限定类型的变量 extern ,也未曾声明具有外部
    链接;


反映 constexpr 意味着对象的const限定。






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



长回答:



在当前工作草案中,N4431,放松了对非类型模板参数的约束。 [14.3.2p1]现在说:


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




  • 一个子对象(1.8),

  • 一个临时对象(12.2)

  • 字符串文字(2.13.5),

  • typeid 表达式的结果(5.2.8)或

  • 预定义的 __ func __ 变量(8.4.1)。


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



同样相关的是,根据[5.20p2.7],应用到

$的左值到右值转换 b
$ b


一个非易失性glvalue,指的是一个非易失性对象定义
with constexpr ,或者指这样的
对象的非可变子对象


也满足作为常数表达式。这允许我们使用一些 constexpr 指针变量作为非类型模板参数。 (注意,简单地声明一个变量 const 是不够的,因为它可以用非常量表达式初始化。)



所以,像 constexpr const char * str3 = str1; 就好了。它被Clang 3.6.0在C ++ 1z模式中接受(并且在C ++ 14模式中被拒绝); GCC 5.1.0仍然拒绝它 - 它似乎还没有实施更新的规则。






字符串字面量错误?这里是问题(N4431 [2.13.5p16]):


评估 string-literal 字面对象与
静态存储持续时间,从给定字符初始化为
上面指定。是否所有字符串文字都是不同的(也就是说,
存储在非重叠对象中),以及是否对 string-literal 的连续
求值产生相同或不同的对象
未指定。


一个实现允许用字符串文字做很多事情:mix,match,部分),从同一翻译单位7份,无论什么。这使得字符串文字的地址不能用作非类型模板参数。


I am very well aware that passing directly a const char* as a template non-type parameter is erroneous, since two identical string literals defined in two different translation units may have different addresses (although most of the time the compilers use the same address). There is a trick one may use, see code below:

#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
}

Basically in (1) we declare and define an array with external linkage, which can then be used as a template parameter in (1'). I understand this very well. However, I don't understand:

  1. Why the constexpr version (2) works? Do constexpr have external linkage? If not, then defining the same string literal in a different translation unit may lead with duplicate template instantiation.

  2. Why (3) and (4) don't work? It seems perfectly reasonable for me, but the compiler doesn't believe so:

    error: 'str3' is not a valid template argument because 'str3' is a variable, not the address of a variable

解决方案

1. Short answer: It works irrespective of it being declared constexpr, because you're defining an object with static storage duration (that is not a string literal - it stores a copy of the contents of one), and its address is a constant expression. Regarding linkage, str2 has internal linkage, but that's fine - its address can be used as a non-type template argument.

Long answer:

In C++11 and 14, [14.3.2p1] says the following:

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;

[...]

So, you can use the address of an object with static storage duration, but the object has to be identified by a name with linkage (internal or external), and the way you're expressing that address is restricted. (String literals are not names and don't have linkage.)

In short, even char str1[] = "Test 1"; works. static char str1[] = "Test 1"; is fine as well; GCC 5.1.0 rejects it, but I think that's a bug; Clang 3.6.0 accepts it.


About str2's linkage, C++11 and 14 [3.5p3] says:

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 has changed that slightly, as a result of DR 1686, to:

  • a variable of non-volatile const-qualified type that is neither explicitly declared extern nor previously declared to have external linkage;

reflecting the fact that constexpr implies const-qualification for objects.


2. Short answer: For C++11 and 14, see above; for draft C++1z, str3 is not a constant expression, as the pointer itself is not constexpr, and it's also the address of a string literal. str4 is constant, but still an address of a string literal.

Long answer:

In the current working draft, N4431, the constraints on non-type template arguments have been relaxed. [14.3.2p1] now says:

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).

And those are all the restrictions. The converted constant expression part is pretty important; the full definition is long, but one part relevant to our case is that the address of an object with static storage duration is such an expression.

Also relevant is that, according to [5.20p2.7], an lvalue-to-rvalue conversion applied to

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

also satisfies the conditions for being a constant expression. This allows us to use some constexpr pointer variables as non-type template arguments. (Note that simply declaring a variable const is not enough, as it can be initialized with a non-constant expression.)

So, something like constexpr const char* str3 = str1; is fine. It's accepted by Clang 3.6.0 in C++1z mode (and rejected in C++14 mode); GCC 5.1.0 still rejects it - it looks like it hasn't implemented the updated rules yet.


Still, what's wrong with string literals? Here's the problem (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.

An implementation is allowed to do lots of things with string literals: mix, match, make them overlap (entirely or partially), make 7 copies from the same translation unit - whatever. That makes the address of a string literal unusable as a non-type template argument.

这篇关于使用const char *作为非类型参数的模板技巧的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆