3

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.

geza
  • 28,403
  • 6
  • 61
  • 135

2 Answers2

3

This is a GCC bug; the address of a template parameter object (which exists only for template parameters of class type!) is certainly a constant expression which can be used as another template argument. Template parameter objects are explicitly mentioned as being usable in constant expressions ([expr.const]/4.5).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • I think expr.const/4.5 doesn't explain everything, because I use the address of a template parameter object. Just as you say, there is some difference how C++ handles class and non-class types in this regard. expr.const/4.5 says no words about this. If I used a non-class type, my code probably would be ill-formed, so there should be some words somewhere else which cause this difference. – geza Jun 17 '23 at 10:42
  • `static_cast('a')`, `*"abcd"` and `__func__[0]` are all lvalue chars that are usable in a constant expression, but that is not enough to use them as a reference/pointer in a non-type template-parameter – Artyer Jun 17 '23 at 11:54
  • @geza: Of course there are words that explain that difference in [expr.prim.id.unqual]/3, but your question simply assumed that taking the reference works, so I merely called out the assumption rather than treating it as the question. – Davis Herring Jun 17 '23 at 16:38
  • @Artyer: Well, yes, you have to follow [temp.arg.nontype]/3 as well. [expr.const]/13 is actually more relevant too; I cited /4.5 because it explicitly **mentions** template parameter objects, not because it’s the only relevant rule. – Davis Herring Jun 17 '23 at 16:38
  • @DavisHerring: I upvoted your answer, and accepted Artyer's, both answers contain relevant information (I feel that [temp.arg.nontype]/3 is more relevant in this case). – geza Jun 17 '23 at 23:05
3

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.

Artyer
  • 31,034
  • 3
  • 47
  • 75