c++templatesc++20compiler-specific

Can the address of a template parameter be used in instantiating another template?


I read a trick somewhere how to convert a constant literal value to a static variable (this can be useful in a template). Check out the makeStatic function in my example code which does this.

I tried to use the result of makeStatic to another call to makeStatic:

template <auto VALUE>
consteval const auto &makeStatic() {
    return VALUE;
}

struct Foo {
};

struct Bar {
    const Foo *x;
};

Foo foo;

int main() {
    constexpr Bar bar{&makeStatic<Foo{}>()};
    // constexpr Bar bar{&foo};

    makeStatic<bar>();
}

This example is compiled by clang and MSVC, but gcc rejects it (godbolt):

<source>: In function 'int main()':
<source>:19:20: error: no matching function for call to 'makeStatic<bar>()'
   19 |     makeStatic<bar>();
      |     ~~~~~~~~~~~~~~~^~
<source>:2:23: note: candidate: 'template<auto VALUE> consteval const auto& makeStatic()'
    2 | consteval const auto &makeStatic() {
      |                       ^~~~~~~~~~
<source>:2:23: note:   template argument deduction/substitution failed:
<source>:19:20: error: the address of 'Foo()' is not a valid template argument
   19 |     makeStatic<bar>();
      |     ~~~~~~~~~~~~~~~^~

Which compiler is correct?

Note: if you use the other bar definition, which uses a global variable to initialize bar, then gcc compiles the code.

Update: I found a similar bug report in gcc's bugzilla, so it seems this is a gcc bug. I'm still wondering where the standard guarantees that my code is well-formed.


Solution

  • This is the complete list of things not allowed in a pointer/reference object/subobject of a template parameter ([temp.arg.nontype]p3):

    For a non-type template-parameter of reference or pointer type, or for each non-static data member or reference or pointer type in 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.

    A template parameter object is not a temporary, since it was never a prvalue ([class.temporary]p1):

    Temporary objects are created

    • when a prvalue is converted to an xvalue ([conv.rval]),
    • when needed by the implementation to pass or return an object of trivially copyable type (see below), and
    • when throwing an exception ([except.throw]).

    And it definitely isn't a string literal object / result of a typeid expression / __func__ / a subobject, so it should be accepted.