将非常量左值引用绑定到推导的非类型模板参数
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.
考虑一个函数模板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 typeconst 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.