c++language-lawyerc++20compiler-bugnon-type-template-parameter

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.


Solution

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