简体   繁体   中英

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

Consider a function template f that binds a non-const lvalue reference to a deduced non-type template parameter

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

This works when f is instantiated over class types

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

But doesn't work when instantiated over non class types (as I expected)

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

The same error is emitted if an lvalue argument is used to instantiate f as well

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

Here's a demo to play with.

This looks like non-type template parameters of class type are lvalues, which seems odd. Where does the standard make a distinction between these kinds of instantiations, and why is there a difference if the argument to the template is of class type?

The reason for the distinction is because class non-type template parameters weren't always there. Originally, value template parameters could only be pointers, integers, or a few other things. Such parameters were simple and the value was just a single number known at compile-time. As such, making them rvalues (remember: prvalue is C++11) was OK.

Once NTTPs could be more complex object types, you have to start engaging with certain questions. Should you be able to do this:

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

The obvious answer is "of course you should." But that would require that you can get a reference to arr itself. That's how range-based for is defined, after all.

Now that can still work. After all, range-based for uses auto&& to store its reference, so it can reference a prvalue. But that has consequences.

Namely, if you create a reference to a prvalue, that causes the materialization of a temporary. A new temporary object distinct from all other objects.

This means that if you use a class NTTP in multiple places, you will get different objects with different addresses and different addresses for their subobjects. And this is something you can detect , since you can get the addresses of their subobjects.

Forcing compile-time code to create temporaries every time you use the name is bad for performance. So two different uses of such a parameter need to result in talking about the same object.

Therefore, class NTTPs need to be lvalues; each use of the name within a template is referring to the same object. But you can't go back and make all existing NTTPs lvalues too; that would break existing code.

So that's where we are.

As for where this is defined, it is in [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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM