Title
When are constexpr member functions defined?
Status
cd5
Section
6.3 [basic.def.odr]
Submitter
Richard Smith

Created on 2012-10-29.00:00:00 last changed 47 months ago

Messages

Date: 2013-04-15.00:00:00

Notes from the April, 2013 meeting:

An additional question was raised about whether constexpr functions should be instantiated as a result of appearing within unevaluated subexpressions of constant expressions. For example:

  #include <type_traits>

  template <class T> constexpr T f(T t) { return +t; }

  struct A { };

  template <class T>
  decltype(std::is_scalar<T>::value ? T::fail : f(T()))
    g() { }

  template <class T>
  void g(...);

  int main()
  {
    g<A>();
  }

If constexpr instantiation happens during constant expression evaluation, f<A> is never instantiated and the program is well-formed. If constexpr instantiation happens during parsing, f<A> is instantiated and the program is ill-formed.

Date: 2017-10-15.00:00:00

[Accepted as a DR as paper P0859R0 at the October, 2017 meeting.]

11.4.4 [special] is perfectly clear that special member functions are only implicitly defined when they are odr-used. This creates a problem for constant expressions in unevaluated contexts:

   struct duration {
     constexpr duration() {}
     constexpr operator int() const { return 0; }
   };
   // duration d = duration(); // #1
   int n = sizeof(short{duration(duration())});

The issue here is that we are not permitted to implicitly define constexpr duration::duration(duration&&) in this program, so the expression in the initializer list is not a constant expression (because it invokes a constexpr function which has not been defined), so the braced initializer contains a narrowing conversion, so the program is ill-formed.

If we uncomment line #1, the move constructor is implicitly defined and the program is valid. This spooky action at a distance is extremely unfortunate. Implementations diverge on this point.

There are also similar problems with implicit instantiation of constexpr functions. It is not clear which contexts require their instantiation. For example:

  template<int N> struct U {};
  int g(int);
  template<typename T> constexpr int h(T) { return T::error; }
  template<typename T> auto f(T t) -> U<g(T()) + h(T())> {}
  int f(...);
  int k = f(0);

There are at least two different ways of modeling the current rules:

  • constexpr function instantiation is triggered by constant expression evaluation. In that case, the validity of the above program depends on the order in which that evaluation proceeds:

    • If the LHS of the + is evaluated first, the program might be valid, because the implementation might bail out evaluation before triggering the ill-formed instantiation of h<int>.

    • If the RHS is evaluated first, the program is invalid, because the instantiation fails.

  • constexpr function instantiation is triggered whenever a constexpr function is referenced from an expression which could be evaluated (note that this is not the same as being potentially-evaluated)

These two approaches can be distinguished by code like this:

  int k = sizeof(U<0 && h(0)>);

Under the first approach, this code is valid; under the second, it is ill-formed.

A possible approach to resolving this issue would be to change the definition of “potentially-evaluated” such that template arguments, array bounds, and braced-init-lists (and any other expressions which are constant evaluated) are always potentially-evaluated, even if they appear within an unevaluated context, and to change 13.9.2 [temp.inst] paragraph 3 to say simply that function template specializations are implicitly instantiated when they are odr-used.

A related question is whether putatively constexpr constructors must be instantiated in order to determine whether their class is a literal type or not. See issue 1358.

Jason Merrill:

I'm concerned about unintended side-effects of such a large change to “potentially-evaluated;” I would prefer something that only affects constexpr.

It occurs to me that this is parallel to issue 1330: just like we want to instantiate exception specifiers for calls in unevaluated context, we also want to instantiate constexpr functions. I think we should define some other term to say when there's a reference to a declaration, and then say that the declaration is odr-used when that happens in potentially-evaluated context.

History
Date User Action Args
2020-12-15 00:00:00adminsetstatus: drafting -> cd5
2013-10-14 00:00:00adminsetstatus: open -> drafting
2013-05-03 00:00:00adminsetmessages: + msg4354
2012-10-29 00:00:00admincreate