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
std::isspace
is defined with#define
directives so it's available inconstexpr
contexts. – Jocoseconst char*
, notstd::string
, and for loops and array indexing are also available inconstexpr
... Hence my issue is more about checking the return value of a virtual function, not just a string (const char*
) in general. – Jocoseconstexpr
as in my selected answer accomplishes this. Unless there's a way to read files in aconstexpr
context. Which wouldn't even surprise me at this point. – Jocosestd::embed
– Loquacity