Table of Contents

Two keywords, constexpr and virtual - can those two work together? Virtual implies runtime polymorphism, while constexpr suggests constant expression evaluation. It looks like we have a contradiction, does it?

Read on and see why those conflicting terms might help us get simpler code.

A basic example

Imagine that you work with some product list, and you want to check if a product fits in a given box size:

#include <cassert>

struct Box {
  double width{0.0};
  double height{0.0};
  double length{0.0};
};

struct Product {
  virtual ~Product() = default;

  virtual Box getBox() const noexcept = 0;
};

struct Notebook : public Product {
  Box getBox() const noexcept override {
    return {.width = 30.0, .height = 2.0, .length = 30.0};
  }
};

struct Flower : public Product {
  Box getBox() const noexcept override {
    return {.width = 10.0, .height = 20.0, .length = 10.0};
  }
};

bool canFit(const Product &prod, const Box &minBox) {
  const auto box = prod.getBox();
  return box.width < minBox.width && box.height < minBox.height &&
         box.length < minBox.length;
}

int main() {
  Notebook nb;
  Box minBox{100.0, 100.0, 100.0};
  assert(canFit(nb, minBox));
}

Play @Compiler Explorer

The code above works at runtime and checks if a given product can fit into minBox.

If you wanted similar code to be executed at compile-time in C++17, it wouldn’t be straightforward. The main issue is with the virtual keyword and runtime polymorphism. In C++17, you’d have to replace this with some static polymorphism.

But… in C++20 we can just throw constexpr and everything will work:

struct Box {
  double width{0.0};
  double height{0.0};
  double length{0.0};
};

struct Product {
  constexpr virtual ~Product() = default;

  constexpr virtual Box getBox() const noexcept = 0;
};

struct Notebook : public Product {
  constexpr ~Notebook() noexcept {};

  constexpr Box getBox() const noexcept override {
    return {.width = 30.0, .height = 2.0, .length = 30.0};
  }
};

struct Flower : public Product {
  constexpr Box getBox() const noexcept override {
    return {.width = 10.0, .height = 20.0, .length = 10.0};
  }
};

constexpr bool canFit(const Product &prod, const Box &minBox) {
  const auto box = prod.getBox();
  return box.width < minBox.width && box.height < minBox.height &&
         box.length < minBox.length;
}

int main() {
  constexpr Notebook nb;
  constexpr Box minBox{100.0, 100.0, 100.0};
  static_assert(canFit(nb, minBox));
}

Play @Compiler Explorer

As you can see, it’s almost a “natural” runtime code but executed at compile time! (checked with static_assert).

The main advantage of the new feature is that you can easily convert your existing code into a compile-time version!

We’re still at the compile-time level, so all types must be known up-front. A similar thing can happen when the compiler performs de-virtualization. But now, the code is explicit and can generate almost no code and work in constant expressions.

More examples and details The Performance Benefits of Final Classes | C++ Team Blog - devirtualization.

Some details

The proposal P1064 added into C++20 simply removes the requirement on constexpr functions:

What’s more, a constexpr function may override a non-constexpr function and vice versa. Depending on the best viable function selection, the compiler can emit an error if the selected function cannot be run at compile-time.

Additionally, there’s a change to the way default destructor is generated:

If this satisfies the requirements of a constexpr destructor, the generated destructor is constexpr

An example

Here’s another example where the new functionality enables us to write simpler code.

There’s a bunch of classes that derive from SectionHandler - each handler works on a different group of tags (for example, tags in some file format). We’d like to see if the tags are not conflicting and unique as a quick compile-time check.

struct SectionHandler {
    virtual ~SectionHandler() = default;

    constexpr virtual std::vector<int> getSupportedTags() const = 0;
};

struct GeneralHandler : public SectionHandler {
    constexpr virtual std::vector<int> getSupportedTags() const override {
        return { 1, 2, 3, 4, 5, 6 };
    }
};

constexpr std::vector<SectionHandler*> PrepareHandlers() {
    return { 
        new GeneralHandler(),
        new ShapesHandler()
    };
}

constexpr size_t checkUniqueTags() {
    auto allHandlers = PrepareHandlers();
    size_t maxTag = 0;

    for (const auto& handler : allHandlers) {
        for (const auto& tag : handler->getSupportedTags())
            if (tag > maxTag)
                maxTag = tag;
    }

    std::vector<int> uniqueTags(maxTag + 1);

    for (const auto& handler : allHandlers) {
        for (const auto& tag : handler->getSupportedTags())
            uniqueTags[tag]++;
    }

    for (auto& handler : allHandlers)
        delete handler;

    auto ret = std::ranges::find_if(uniqueTags, [](int i) { return i >= 2;});

    return ret == uniqueTags.end();
}

int main() {
    static_assert(checkUniqueTags());
}

Play @Compiler Explorer

And here’s another version with two techniques (sorting + std::unique): @Compiler Explorer

Would you like to see more?
I wrote a constexpr string parser and it's available for C++ Stories Premium/Patreon members. See all Premium benefits here.

Even better - parsing expressions

For the purpose of this article I even contacted with the authors of the propsal. And I got a very interesting example:

constexpr char const * expr = "(11+22)*(33+44)";
static_assert( evaluate( expr ) == 2541 );

The code is a basic expression parser that works on compile-time in C++20.

What’s best is that it was converted from a runtime version by just “adding” constexpr here and there :)

Here’s the code funtion, runtime:

int evaluate( std::string_view expr ) {
    char const * first = expr.data();
    char const * last = expr.data() + expr.size();

    Node* n = parse_expression( first, last );

    int r = n->evaluate();

    delete n;

    return r;
}

And compare it with the constexpr version:

constexpr int evaluate( std::string_view expr ) {
    char const * first = expr.data();
    char const * last = expr.data() + expr.size();

    Node* n = parse_expression( first, last );

    int r = n->evaluate();

    delete n;

    return r;
}

See the runtime version @Compiler Explorer, and the constexpr approach @Compiler Explorer.

With the permission of Peter Dimov.

Potential optimization

This feature is very fresh, and the early implementations are interesting. For example, under MSVC, you have even experimental flags.

under /experimental:constevalVfuncVtable and /experimental:constevalVfuncNoVtable

Once a decision is made on how to proceed, we’ll bring that capability under /std:c++20 and /std:c++latest.

See more in: MSVC C++20 and the /std:c++20 Switch | C++ Team Blog

Summary

While adding constexpr to a virtual function sounds scary at first sight, it looks like the new technique allows us to reuse code from the runtime version.

For now, I can imagine use cases where you can write some compile-time checks for your classes and class hierarchies. For example, with those file tag handling. The final production code is executed at runtime, but you might have some benefits of early “pre-flight” checks for development.

And another use case is for porting of existing algorithms from the runtime version to compile-time.

You can read more in the proposal P1064

Back to you

  • Do you try to make your types and classes constexpr-ready?
  • Do you have any use cases where constexpr helped?

Let us know in the comments below the article.