指针非类型模板参数

vso*_*tco 17 c++ templates c++11

我真的不明白为什么下面的代码不能编译:

template<const char*>
struct Foo{};

constexpr const char s1[] = "test1";
constexpr const char* const s2 = "test2";

int main()
{
    Foo<s1> foo1; // ok
    // Foo<s2> foo2; // doesn't compile
}
Run Code Online (Sandbox Code Playgroud)

取消注释最后一main()行使g ++和clang ++发出错误

error: 's2' is not a valid template argument because 's2' is a
variable, not the address of a variable
Run Code Online (Sandbox Code Playgroud)

error: non-type template argument for template parameter of
      pointer type 'const char *' must have its address taken
Run Code Online (Sandbox Code Playgroud)

分别.

我的问题是:

  1. 为什么s1实例化s2没问题呢?
  2. 是否有任何理智的情况,这种指针非类型模板参数是否有用?

Quu*_*one 7

在上面的评论中,vsoftco补充道:

看起来非常奇怪,afaik字符串文字不是临时文件,但是在程序的整个持续时间内存储,因此它们的地址肯定是编译时间常数(或者至少是我认为的那样)

确实如此.但是,该标准未指定字符串文字是否具有唯一地址.

一些链接器合并或重复删除字符串文字.我曾经在"ello" == "hello"+1实际评估过的系统上工作过true.其他链接器是如此愚蠢,以至于"hello"foo.cc中的地址与"hello"bar.cc中的地址不同.哎呀,一些小C编译器是如此愚蠢,"hello"可以在同一个翻译单元中有两个不同的地址!

对于这样一个愚蠢的链接器(或编译器),应该Foo<"hello">导致一个或两个实例?那是...

const char *sa = "hello world";
const char *sb = "hello world";
assert(sa != sb);  // this assertion is permitted to succeed

template<char*> struct F {};
F<"hello world"> fa;
F<"hello world"> fb;
assert(!is_same<decltype(fa), decltype(fb)>::value);
    // should we permit this assertion to succeed also?
Run Code Online (Sandbox Code Playgroud)

委员会通过简单地禁止这种结构,令人钦佩地拒绝打开这种蠕虫.


现在,可以想象(对我来说,目前),委员会可以在未来某个时候强制要求所有字符串文字都通过与当前实现inlinetemplate功能相同的机制进行重复数据删除.也就是说,我们可以想象一个转换的源级转换

const char *sc = "yoo hoo";
Run Code Online (Sandbox Code Playgroud)

inline auto& __stringlit_yoo_x20hoo() {
    static const char x[] = "yoo hoo";
    return x;
}
const char *sc = __stringlit_yoo_x20hoo();
Run Code Online (Sandbox Code Playgroud)

然后在程序的任何地方只有一个实例__stringlit_yoo_x20hoo(并且只有该函数的静态数组的单个实例x),因此其含义F<"yoo hoo">将是明确的.实施将不得不名称裂伤的东西明确为好,但一旦你已经承诺名称重整之类的东西,这是一个简单的问题F<1+1>,并F<FruitType,ORANGE>(其中C++编译器已经永远做).

...但是那时你仍然会遇到那些非常聪明的连接器(比如我工作过的那个)的问题

assert("hello" == "hello\0world");  // this assertion is permitted to succeed

assert(!is_same_v< F<"hello">, F<"hello\0world"> >);
    // should we permit this assertion to succeed also?
    // Surely this way lies madness.
Run Code Online (Sandbox Code Playgroud)


use*_*267 6

1:

来自[temp.arg.nontype]

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

[...]

(1.3) - 字符串文字(2.13.5),

s2保存字符串文字的地址,因此不能在此处用作参数.s1另一方面,它是一个char已使用字符串文字初始化的数组,但s1(转换为const char*)时的值不指向初始化中使用的字符串文字.

2:

函数指针也许?我仍然不能说我曾经使用指针作为非类型参数.


T.C*_*.C. 6

最近有相关标准文本发生了变化,但在两个版本的标准中都不接受该代码.

N4140 [temp.arg.nontype]/p1:

1.一种模板参数的用于非类型的,非模板的模板参数应是以下之一:

  • 用于非类型模板参数整数或枚举类型,类型的转换后的常量表达式(5.19)的 模板的参数 ; 要么
  • 非类型模板参数的名称 ; 要么
  • 一个常量表达式(5.19),用于指定具有静态存储持续时间和外部或内部链接的完整对象的地址,或具有外部或内部链接的函数,包括函数模板和函数模板ID,但不包括非静态类成员,表达(忽略括号)as & id-expression,其中id-expression是对象或函数的名称,除非&如果名称引用函数或数组可以省略,如果相应的template-parameter是引用则应省略 ; 要么
  • 一个常量表达式,其值为空指针值(4.10); 要么
  • 一个常量表达式,其值为null成员指针值(4.11); 要么
  • 指向成员的指针,如5.3.1所述; 要么
  • 类型的常量表达式std::nullptr_t.

N4296 [temp.arg.nontype]/p1:

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

  • 子对象(1.8),
  • 一个临时物体(12.2),
  • 字符串文字(2.13.5),
  • typeid表达式(5.2.8)的结果,或
  • 预定义__func__变量(8.4.1).

N4140版本是目前由编译器实现的版本.N4296版本稍微宽松一些,但在两种情况下,字符串文字的地址都不是可接受的模板参数.

可能这样做的部分原因是模板参数必须被修改,并且以一种理智的方式修改字符串文字,这将在多个翻译单元中工作,这将是非常困难的,如果不是不可能的话.