I was reading the C++ docs the other day and noticed that, although literal types must not have virtual members, this does not prevent them from implementing virtual members. Or at least that's what I understood.
This is some piece of code I've been playing with:
#include <cassert>
// Some forward declarations:
enum class literal_id;
struct literal_base;
struct literal_a;
struct literal_b;
// Now some definitions:
enum class literal_id {
a, b
};
struct literal_base {
virtual literal_id method() const noexcept = 0;
};
struct literal_a : public literal_base {
constexpr literal_id method() const noexcept final { return literal_id::a; }
constexpr operator literal_b() const noexcept;
};
struct literal_b : public literal_base {
constexpr literal_id method() const noexcept final { return literal_id::b; }
constexpr operator literal_a() const noexcept;
};
constexpr literal_a::operator literal_b() const noexcept { return literal_b(); }
constexpr literal_b::operator literal_a() const noexcept { return literal_a(); }
// Some test methods
literal_id process_literal_base(literal_base const& l) { return l.method(); }
constexpr literal_id process_literal_a(literal_a const& l) { return l.method(); }
constexpr literal_id process_literal_b(literal_b const& l) { return l.method(); }
// Some test variables
constexpr auto a = literal_a();
constexpr auto b = literal_b();
int main() {
// Compile-time tests, all ok
static_assert(process_literal_a(b) == literal_id::a, "");
static_assert(process_literal_b(a) == literal_id::b, "");
// Runtime tests, all ok
assert(process_literal_base(a) == literal_id::a);
assert(process_literal_base(b) == literal_id::b);
return 0;
}
Some remarks:
literal_base
with an implicit (and therefore trivial) destructor, because none of its subclasses are supposed to have anything but a trivial destructor -- they are literal types, after all.literal_base
has a single method
function used for testing, but the intention is for it to have as many pure virtual functions as needed (non-virtual final functions are also valid).method
overrides in the subclasses are marked final
, even though they are not marked virtual
. This is just to silence the compiler, because those classes are supposed to either be leaf (in their inheritance tree) or have no overwritten functions. (All this has to do with pre-C++11 undefined behaviour semantics for final implementations of virtual functions, when the final
specifier did not exist yet.)process_*
functions were created to help with asserting the correctness of the implementation at compile and runtime.Some relevant definitions for literal types:
... possibly cv-qualified class type that has all of the following properties:
- (1) has a trivial destructor. [[ they have (the subclasses of
literal_base
, I mean) ]]- (2) is either
- (2.1) an aggregate type, [[ not applicable ]]
- (2.2) a type with at least one constexpr (possibly template) constructor that is not a copy or move constructor, [[ it has, but just because none of the classes have an explicit constructor; but it's easy to achieve ]]
- (2.3) a closure type (since C++17) [[ not applicable ]]
- (3) for unions, at least one non-static data member is of non-volatile literal type, [[ not applicable ]]
- (4) for non-unions, all non-static data members and base classes are of non-volatile literal types. (since C++17) [[ no
volatile
in the example, and neither shouldvolatile
be used in a real application; also,literal_base
's subclasses are supposed to be literal types, so this rule must (and can) be applied ]]- (5) all non-static data members and base classes are of non-volatile literal types. [[ same as (4), basically ]]
Now some definitions for constexpr functions:
- it must not be virtual [[ none of the subclasses have virtual functions; all of them are final, and therefore their locations are known without the need for e.g. vtables ]]
- (...)
Am I right to assume all that? Is there anything about the specification I'm overlooking?
A constexpr function must accept and return only literal types. A constexpr function can be recursive. It can't be virtual.
A non-static data member cannot be constexpr. static constexpr int x = 5; int y; };
Using constexpr to Improve Security, Performance and Encapsulation in C++ constexpr is a new C++11 keyword that rids you of the need to create macros and hardcoded literals. It also guarantees, under certain conditions, that objects undergo static initialization.
The rule in [dcl.constexpr] is pretty clear:
The definition of a
constexpr
function shall satisfy the following requirements:
— it shall not be virtual (10.3);
literal_a::method
and literal_b::method
are both virtual
because each they override literal_base::method
, which is virtual
. Hence, they cannot be constexpr.
It does not matter that they are final
. The program is ill-formed.
It is true that a literal type is allowed to have a virtual
member function though.
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