Suppose I have a struct
template S
that is parametrized by an engine:
template<class Engine> struct S;
I have two engines: a "static" one with a constexpr
member function size()
, and a "dynamic" one with a non-constexpr
member function size()
:
struct Static_engine {
static constexpr std::size_t size() {
return 11;
}
};
struct Dynamic_engine {
std::size_t size() const {
return size_;
}
std::size_t size_ = 22;
};
I want to define size()
member function in S
that can be used as a constexpr
if the engine's size()
is constexpr
. I write:
template<class Engine>
struct S {
constexpr std::size_t size() const {
return engine_.size();
}
Engine engine_;
};
Then the following code compiles with GCC, Clang, MSVC and ICC:
S<Static_engine> sta; // not constexpr
S<Dynamic_engine> dyn;
constexpr auto size_sta = sta.size();
const auto size_dyn = dyn.size();
Taking into account intricacies of constexpr
and various "ill-formed, no diagnostic is required", I still have the question: is this code well-formed?
Full code on Godbolt.org
(I tagged this question with both c++17 and c++20 in case this code has different validity in these two standards.)
The code is fine as written.
[dcl.constexpr]
6 If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression. If no specialization of the template would satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed, no diagnostic required.
The member may not appear in a constant expression for the specialization that uses Dynamic_engine
, but as the paragraph above details, that does not make S::size
ill-formed. We are also far from ill-formed NDR territory, since valid instantations are possible. Static_engine
being a prime example.
The quote is from n4659, the last C++17 standard draft, and similar wording appears in the latest C++20 draft.
As for the evaluation of sta.size()
as a constant expression, going over the list at [expr.const] I cannot find anything that is disallowed in the evaluation itself. It is therefore a valid constant expression (because the list tells us what isn't valid). And in general for a constexpr
function to be valid, there just needs to exist some set of arguments for which the evaluation produces a valid constant expression. As the following example form the standard illustrates:
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
struct B {
constexpr B(int x) : i(0) { } // x is unused
int i;
};
int global;
struct D : B {
constexpr D() : B(global) { } // ill-formed, no diagnostic required
// lvalue-to-rvalue conversion on non-constant global
};
Yes.
Functions may be marked as constexpr
without being forced to be evaluated at compile-time. So long as you satisfy the other requirements for marking a function as constexpr
, things are okay (returns a literal type, parameters are literals, no inline asm, etc.). The only time you may run into issues is if it's not actually possible to create arguments that satisfy the function being called as a core constant expression. (e.g., if your function had undefined behavior for all values, then your function would be ill-formed NDR)
In C++20, we received the consteval
specifier that forces all calls to the function to be able to produce a compile-time constant (constexpr
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With