Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does this constexpr virtual function technique violate any C++11/C++14 rule?

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:

  • I have a base class 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).
  • Note that the 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.)
  • The process_* functions were created to help with asserting the correctness of the implementation at compile and runtime.
  • I also played with value semantics, for no reason at all, and all was well :)

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 should volatile 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?

like image 361
Flávio Lisbôa Avatar asked Mar 09 '17 03:03

Flávio Lisbôa


People also ask

Can virtual functions be constexpr?

A constexpr function must accept and return only literal types. A constexpr function can be recursive. It can't be virtual.

Does constexpr have to be static?

A non-static data member cannot be constexpr. static constexpr int x = 5; int y; };

Does constexpr improve performance?

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.


1 Answers

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.

like image 188
Barry Avatar answered Sep 19 '22 12:09

Barry