nokz
nokz

Reputation: 175

Is it possible to bind a class type object to a reference through a non-type template parameter in C++20?

Consider the following code:

struct wrapper {
    int& ref;
    constexpr wrapper(int& ref) : ref(ref) {}
};

template <auto&& X>
void fun1() {}

template <wrapper X>
void fun2() {
    fun1<X>();
}

int main() {
    static int val = 22;
    fun2<val>();
}

fun2<X>() takes in a class as non-type template parameter while fun1<X>() takes in a forwarding reference to any object passed to it.

The above fails to compile in GCC with the error shown below but compiles just fine in Clang.

<source>:11:12: error: the address of 'wrapper{val}' is not a valid template argument
   11 |     fun1<X>();
      |     ~~~~~~~^~

Which compiler is correct here? Is this supposed to be a bug in either of the two compilers?

I tried to change template <wrapper X> to template <const wrapper& X>, but to no avail, as it fails on both GCC and Clang.

Upvotes: 0

Views: 134

Answers (1)

Jan Schultke
Jan Schultke

Reputation: 39658

Your code is valid. This is possibly a GCC bug. I've submitted a bug report, see GCC Bug 113242 - g++ rejects-valid template argument of class type containing an lvalue reference.

For a non-type template-parameter of reference or pointer type, or for each non-static data member of reference or pointer type in a non-type template-parameter of class type or subobject thereof, the reference or pointer value shall not refer to or be the address of (respectively):

  • a temporary object ([class.temporary]),
  • a string literal object ([lex.string]),
  • the result of a typeid expression ([expr.typeid]),
  • a predefined __func__ variable ([dcl.fct.def.general]), or
  • a subobject ([intro.object]) of one of the above.

- [temp.arg.nontype] p3

The reference auto&& X refers to an object wrapper X that is none of those, so it should be a valid template argument.

Note that what happens in main is effectively:

int main() {
    static int val = 22;       // OK, obviously
    constexpr int& ref = val;  // OK, can bind reference to static storage duration object
    constexpr wrapper w = ref; // OK, can create object containing such reference
    fun2<w>();                 // OK, but GCC errors in fun2's definition
}

Also note that the unqualified-id X in fun1<X>() is an lvalue of type const wrapper ([expr.prim.id.unqual] p2). Therefore, the reference binding is direct and no temporaries are involved.

Second Example

Both compilers are correct in rejecting

template <const wrapper& X>
void fun2() { /* ... */ }

int main() {
    static int val = 22;
    fun2<val>();
}

... because fun2<val>() makes X bind to a temporary wrapper object, created within the function call to fun2.

Upvotes: 2

Related Questions