I'm confused about what I read about mixing virtual
and constexpr
for member functions.
According to:
up to C++17 included, it should not be possible to mix constexpr
and virtual
(standard is quoted in above links)
Yet I designed this straightfoward example:
#include <cstddef>
struct SizedObject {
virtual size_t GetSize() const = 0;
};
struct DynSizedObject : public SizedObject {
size_t s;
size_t GetSize() const override final { return s; }
};
struct StatSizedObject : public SizedObject {
const size_t s;
constexpr size_t GetSize() const override final { return s; }
constexpr explicit StatSizedObject(const size_t i) : s(i) {}
};
int main(int argc, char** argv) {
constexpr StatSizedObject SS(42);
DynSizedObject DS;
DS.s = argc + 2;
SizedObject const * p;
if (argc > 3) {
p = &SS;
} else {
p = &DS;
}
return p->GetSize();
}
I'm designing a hierarchy of objects with a size property that can be retrieved through the GetSize
member function, which I make virtual
. Now one of the concrete class can be instantiated with a compile-time size, and I make its GetSize
override constexpr
. Then my toy example trick the compiler in not knowing, at compile-time, which object will call GetSize
. In the Live I'm surprised to see that gcc accept the code. clang reject it, seemingly consistant with the standard, while msvc do as clang but also claim that the constexpr
version cannot result in a constant expression, which seems incorrect to me.
Thus my understanding would be that all compiler should behave like clang (but don't): is it correct? In this case, is it a gcc "bug" that it is accepting this construct? subsidiary question: why does msvc issue the cannot result in a constant expression error?
Beyond an academic interest, my purpose is to know if I may use some compile-time optimisation technics in relation with dynamic polymorphism (up to C++17, I know that, from C++20, some designs are possible).
In C++17, the above code would be ill-formed. What you have discovered is a compiler bug in GCC. The standard is very clear on this:
The definition of a constexpr function shall satisfy the following requirements:
- it shall not be virtual;
- [...]
If a virtual member function
vf
is declared in a classBase
and in a classDerived
, derived directly or indirectly fromBase
, a member functionvf
with the same name, parameter-type-list, cv-qualification, and ref-qualifier (or absence of same) asBase::vf
is declared, thenDerived::vf
is also virtual (whether or not it is so declared) and it overrides111Base::vf
.
From these two sections we can conclude that constexpr virtual
functions are disallowed, and any function that overrides a virtual
function also cannot be constexpr
, because it is implicitly virtual
.
Interestingly enough, we get no diagnostics for GetSize
being virtual constexpr
, even though it is a virtual function according to the standard.
// implicitly virtual because of it overrides SizedObject::GetSize
constexpr size_t GetSize() const override final { return s; }
If we mark the function virtual
redundantly, we do get diagnostics:
constexpr virtual size_t GetSize() const override final { return s; }
<source>:14:15: warning: member 'GetSize' can be declared both 'virtual' and > 'constexpr' only in '-std=c++20' or '-std=gnu++20' [-Wc++20-extensions] 14 | constexpr virtual size_t GetSize() const override final { return s; } | ~~~~~~~~~ ^~~~~~~
As for:
subsidiary question: why does msvc issue the cannot result in a constant expression error?
Admittedly, this is not a very intuitive error message. It's simply what MSVC outputs for all cases where a function cannot be constexpr
because of its signature. You get the same message when making the return type of a constexpr
function std::vector<int>
.
In general, constexpr
means that at least for some arguments, a function has to be able to result in a constant expression. If the function signature makes that impossible, then specifying constexpr
is wrong.
C++20 lifted these restrictions on constexpr virtual
functions, so the above code would be well-formed.