Search code examples
c++virtualconstexpr-function

virtual and constexpr before up to C++17


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).


Solution

  • C++17

    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:

    - [dcl.constexpr] p3

    If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list, cv-qualification, and ref-qualifier (or absence of same) as Base​::​vf is declared, then Derived​::​vf is also virtual (whether or not it is so declared) and it overrides111 Base​::​vf.

    - [class.virtual] p2

    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.

    GCC Diagnostics

    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; }
          |     ~~~~~~~~~ ^~~~~~~
    

    MSVC Diagnostics

    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

    C++20 lifted these restrictions on constexpr virtual functions, so the above code would be well-formed.