October 22, 2024
Chicago 12, Melborne City, USA
templates

(Reverse) forwarding consteval-ness of constructor with perfect forwarding


Let’s say I have a class with a consteval constructor that ensures a valid value at compile time. (There’s also a std::expected-returning factory method that validates its argument at runtime, but that’s irrelevant for this question.)

class ConstevalConstructible {
 public:
  consteval ConstevalConstructible(int value) : value_(value) {
    // Validate value and fail compile if invalid.
  }
  // Copyable, movable, et cetera
 private:
  int value_;
}

Now let’s say I have a generic wrapper struct that can hold any value:

template <typename T>
struct Wrapper {
  template <typename... Args>
  constexpr Wrapper(Args&&... args) : inner(std::forward<Args>(args)...) {}

  T inner;
};

Since constexpr allows the constructor to be invoked in a constant context, the wrapper works fine for both runtime-construction and constexpr construction:

// Fine
Wrapper<RuntimeConstructible> foo{runtime_value};
// Also fine: Wrapper's constexpr constructor is evaluated at compile
// time and thus the ConstexprConstructible's constructor is too.
constexpr Wrapper<ConstexprConstructible> bar{42};

The trouble occurs when using ConstevalConstructible with Wrapper:

// Fails to compile: the constexpr Wrapper constructor is not an
// immediate function, so it cannot invoke ConstevalConstructible's
// constructor using arguments that are not (within its body)
// constexpr.
Wrapper<ConstevalConstructible> foo{5};
// Doesn't fix the problem from the compiler's point of view.
constinit Wrapper<ConstevalConstructible> foo{5};
// This does work as long as ConstevalConstructible is movable or
// copyable, but gets annoying.
Wrapper<ConstevalConstructible> bar{ConstevalConstructible{5}};

The problem is that while constexpr evaluation propagates out-to-in (the constexpr on the variable declaration causes Wrapper‘s constructor to be compile-time evaluated which in turn causes ConstexprConstructible‘s constructor to be compile-time evaluated), constructor consteval-ness would instead need to propagate in-to-out (ConstevalConstructible‘s constructor being consteval would have to cause Wrapper‘s constructor to also be consteval so the whole construction of Wrapper is immediately evaluated).

This feels analogous to how noexcept works, where whether a method throws can be specified in terms of the contained type. However, I’m not aware of a similar way to make a constructor or function be conditionally consteval. I considered adding an consteval overload, but I’m not sure how to constrain it:

template <typename T>
struct Wrapper {
  template <typename... Args>
  constexpr Wrapper(Args&&... args) : inner(std::forward<Args>(args)...) {}
  template <typename... Args>
  consteval Wrapper(Args&&... args)
    requires requires { /** What would I put here? **/ }
    : inner(std::forward<Args>(args)...) {}

  T inner;
};

Is this solvable in C++20? If not, are there plans for it to be in a future version?



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video