Let’s try C++20 | virtual constexpr functions
Once, I wondered if we could have virtual constexpr functions. It is now possible with C++20 so let’s try!
A Piece of Personal Experience#
Two years ago, I had several classes with a common point: base classes had a pure virtual function to request derived classes to return a constant. Something like this:
struct Memory {
// Capacity in bytes
virtual unsigned int capacity() const = 0;
};
It was the base class for drivers to communicate with a particular memory chip. Each subclass would return the size of the actual memory it handles. For instance, for the Microchip’s 25LC160C EEPROM:
struct EEPROM_25LC160C : Memory {
unsigned int capacity() const override {
return 2048;
}
};
I asked myself: can I make this function constexpr because the size never changes for each class? Answer: it was not possible back then.
Now with virtual constexpr#
But guess what? This is now possible in C++20:
A
constexprfunction must satisfy the following requirements:
- it must not be virtual (until C++20)
- (…)
Code can now be:
struct Memory {
virtual constexpr unsigned int capacity() const = 0;
};
struct EEPROM_25LC160C : Memory {
constexpr unsigned int capacity() const override {
return 2048;
}
};
Note that it is possible for the base function to be constexpr while the derived function is not. And vice versa.
Examples#
Great! But wait… How is this useful for me? Well, actually, for the moment, I don’t have a clue 😅
Code like this works:
constexpr EEPROM_25LC160C eeprom;
static_assert(eeprom.capacity() > 1024);
Before virtual constexpr, I would have to provide a public static constexpr member like this:
struct EEPROM_25LC160C : Memory {
static constexpr auto SIZE = 2048;
unsigned int capacity() const override {
return SIZE;
}
};
static_assert(EEPROM_25LC160C::SIZE > 0);
C++20’s version is an improvement but I think it’s minor.
Another possible code:
static constexpr EEPROM_25LC160C eeprom;
constexpr const Memory& getMemory() {
return eeprom;
}
int main() {
constexpr const Memory& memory = getMemory();
static_assert(memory.capacity() > 1024);
}
OK… But without the consts it doesn’t compile and having const objects is not necessarily what I want.
Here is an example that doesn’t compile (obviously):
template<unsigned int startAddress, unsigned int bytesCount>
class Serializer {
private:
EEPROM_25LC160C eeprom;
static_assert(startAddress + bytesCount < eeprom.capacity());
};
int main() {
constexpr EEPROM_25LC160C eeprom;
Serializer<1500, 700> serializer;
}
<source>:15:47: error: invalid use of non-static data member 'memory'
static_assert(startAddress + bytesCount < memory.capacity());
^~~~~~
I can try something else:
template<unsigned int startAddress, unsigned int bytesCount>
class Serializer {
public:
constexpr Serializer() {
static_assert(startAddress + bytesCount < eeprom.capacity());
}
private:
EEPROM_25LC160C eeprom;
};
int main() {
constexpr EEPROM_25LC160C eeprom;
constexpr Serializer<1500, 700> serializer;
}
Still, the code doesn’t compile:
<source>:15:23: error: static_assert expression is not an integral constant expression
static_assert(startAddress + bytesCount < eeprom.capacity());
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Conclusion#
I have read the paper that proposed to add this feature to the C++ standard: “Allowing Virtual Function Calls in Constant Expressions” by Peter Dimov and Vassil Vassilev.
Clearly, it doesn’t sound like a feature everyone will use in their everyday code. I will keep it in mind and I hope one day I will find a use-case in my code 😁
PS: here is an excellent article from Microsoft about the spaceship operator: Simplify Your Code With Rocket Science: C++20’s Spaceship Operator