将非常量左值引用绑定到推导的非类型模板参数

Binding a non-const lvalue reference to a deduced non-type template parameter

考虑一个函数模板f,它将非常量左值引用绑定到推导的非类型模板参数

template <auto N> 
void f()
{
    auto & n = N;
}

这在 f 实例化超过 class 类型时有效

struct S {};
f<S{}>();    // ok

但是在非 class 类型上实例化时不起作用(如我所料)

f<42>();  // error non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'

如果左值参数也用于实例化 f,也会出现相同的错误

constexpr int i = 42;
f<i>();                // also error

这里有一个 demo 可以玩。

这看起来 class 类型的非类型模板参数是左值,这看起来很奇怪。标准在哪里区分这些类型的实例化,如果模板的参数是 class 类型,为什么会有区别?

区别的原因是 class non-type 模板参数并不总是存在。最初,值模板参数只能是指针、整数或其他一些东西。这些参数很简单,值只是一个在 compile-time 已知的数字。因此,使它们成为右值(记住:prvalue 是 C++11)是可以的。

一旦 NTTP 可能成为更复杂的对象类型,您就必须开始处理某些问题。如果你能做到这一点:

template<std::array<int, 5> arr>
void func()
{
  for(int i: arr)
    //stuff
}

显而易见的答案是“当然应该”。但这需要您可以获得对 arr 本身的引用。毕竟,range-based for 就是这样定义的。

现在仍然可以使用。毕竟,range-based for 使用 auto&& 来存储它的引用,所以它可以引用纯右值。但这会产生后果。

即,如果您创建对纯右值的引用,则会导致临时对象的具体化。 new 不同于所有其他对象的临时对象。

这意味着如果你在多个地方使用一个class NTTP,你会得到不同地址的不同对象和它们的子对象的不同地址。这是你可以检测的东西,因为你可以获得它们的子对象的地址。

强制 compile-time 代码在每次使用该名称时创建临时变量对性能不利。因此,此类参数的两种不同用法需要导致谈论同一对象。

因此,class NTTP 需要是左值;模板中名称的每次使用都指的是同一个对象。但是您不能返回并使所有现有的 NTTP 也成为左值;这会破坏现有代码。

这就是我们所在的位置。

至于this是在哪里定义的,在[temp.param]/8:

An id-expression naming a non-type template-parameter of class type T denotes a static storage duration object of type const T, known as a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template-parameter. All such template parameters in the program of the same type with the same value denote the same template parameter object.