指针非类型模板参数

pointer non-type template parameter

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

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
}

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

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

error: non-type template argument for template parameter of
      pointer type 'const char *' must have its address taken

分别

我的问题是:

  1. 为什么 s1 可以实例化而 s2 不行?
  2. 这种指针非类型模板参数是否有任何用处?

对于 1.:

来自 [temp.arg.nontype]

1 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):

[...]

(1.3) — a string literal (2.13.5),

s2 保存的是字符串字面量的地址,所以这里不能作为参数。另一方面,s1char 的数组,已使用字符串文字 初始化 ,但 s1 的值(当转换为const char*) 未指向初始化中使用的字符串文字。

对于 2.:

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

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

N4140 [temp.arg.nontype]/p1:

1 A template-argument for a non-type, non-template template-parameter shall be one of:

  • for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter; or
  • the name of a non-type template-parameter; or
  • 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; or
  • a constant expression that evaluates to a null pointer value (4.10); or
  • a constant expression that evaluates to a null member pointer value (4.11); or
  • a pointer to member expressed as described in 5.3.1; or
  • a constant expression of type std::nullptr_t.

N4296 [temp.arg.nontype]/p1:

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

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

大概部分原因是模板参数必须被破坏,并且以一种适用于多个翻译单元的合理方式破坏字符串文字将非常困难,如果不是不可能的话。

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

seems extremely weird, afaik string literals are not temporaries but are stored for the whole duration of the program, so their address is for sure a compile time constant (or at least that's what I believe)

没错。但是,该标准并未指定字符串文字是否具有 唯一地址 .

一些链接器合并或删除重复的字符串文字。我曾在 "ello" == "hello"+1 实际计算为 true 的系统上工作过。其他链接器非常愚蠢,以至于 foo.cc 中的 "hello" 与 bar.cc 中的 "hello" 具有不同的地址。哎呀,一些微型 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?

委员会令人钦佩地拒绝打开蠕虫罐头,只是禁止构造。


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

const char *sc = "yoo hoo";

进入

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

那么在程序中的任何地方都只有一个 __stringlit_yoo_x20hoo 的实例(并且只有一个该函数的静态数组 x 的实例),所以 F<"yoo hoo"> 的含义将是明确的。该实现也必须明确地对事物进行名称修改,但一旦您已经致力于名称修改诸如 F<1+1>F<FruitType,ORANGE>(C++ 编译器一直在做的事情),这就是一个简单的问题).

...但是你 仍然 对那些允许

assert("hello" == "hello[=13=]world");  // this assertion is permitted to succeed

assert(!is_same_v< F<"hello">, F<"hello[=13=]world"> >);
    // should we permit this assertion to succeed also?
    // Surely this way lies madness.