I have a base class that is intended to be inherited by other users of the code I'm writing, and one of the abstract functions returns a name for the object. Due to the nature of the project that name cannot contain whitespace.
class MyBaseClass {
public:
// Return a name for this object. This should not include whitespace.
virtual const char* Name() = 0;
};
Is there a way to check at compile-time if the result of the Name()
function contains whitespace? I know compile-time operations are possible with constexpr
functions but I'm not sure of the right way to signal to code users that their function returns a naughty string.
I'm also unclear on how to get a constexpr
function to actually be executed by the compiler to perform such a check (if constexpr
is even the way to go with this).
I think this is possible in C++20.
Here is my attempt:
#include <string_view>
#include <algorithm>
#include <stdexcept>
constexpr bool is_whitespace(char c) {
// Include your whitespaces here. The example contains the characters
// documented by https://en.cppreference.com/w/cpp/string/wide/iswspace
constexpr char matches[] = { ' ', '\n', '\r', '\f', '\v', '\t' };
return std::any_of(std::begin(matches), std::end(matches), [c](char c0) { return c == c0; });
}
struct no_ws {
consteval no_ws(const char* str) : data(str) {
std::string_view sv(str);
if (std::any_of(sv.begin(), sv.end(), is_whitespace)) {
throw std::logic_error("string cannot contain whitespace");
}
}
const char* data;
};
class MyBaseClass {
public:
// Return a name for this object. This should not include whitespace.
constexpr const char* Name() { return internal_name().data; }
private:
constexpr virtual no_ws internal_name() = 0;
};
class Dog : public MyBaseClass {
constexpr no_ws internal_name() override {
return "Dog";
}
};
class Cat : public MyBaseClass {
constexpr no_ws internal_name() override {
return "Cat";
}
};
class BadCat : public MyBaseClass {
constexpr no_ws internal_name() override {
return "Bad cat";
}
};
There are several ideas at play here:
Let's use the type system as documentation as well as constraint. Therefore, let us create a class (no_ws
in the above example) that represents a string without whitespaces.
For the type to enforce the constraints at compile-time, it must evaluate its constructor at compile time. So let's make the constructor consteval
.
To ensure that derived classes don't break the contract, modify the virtual method to return no_ws
.
If you want to keep the interface (i.e returning const char*
), make the virtual method private, and call it in a public non-virtual method. The technique is explained here.
Now of course here I am only checking a finite set of whitespace characters and is locale-independent. I think it would very tricky to handle locales at compile-time, so maybe a better way (engineering-wise) would be to explicitly specify a set of ASCII characters allowed in the names (a whitelist instead of a blacklist).
The above example would not compile, since "Bad cat"
contains whitespace. Commenting out the Bad cat
class would allow the code to compile.
Live demo on Compiler Explorer
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