In our project, we use quite a lot of "usings" to explicitly state what is variable supposed to represent. It is mainly used for std::string
identificators like PortalId
or CakeId
. Now what we currently can do is
using PortalId = std::string;
using CakeId = std::string;
PortalId portal_id("2");
CakeId cake_id("is a lie");
portal_id = cake_id; // OK
which we don't like. We would like to have type check during compile time to prevent us from mixing apples and oranges while preserving most of the yum yum methods from the original object.
So the question is - can this be done in C++ such that the usage would be close to what follows, assignments would fail and we could still use it with, say, maps and other containers?
SAFE_TYPEDEF(std::string, PortalId);
SAFE_TYPEDEF(std::string, CakeId);
int main()
{
PortalId portal_id("2");
CakeId cake_id("is a lie");
std::map<CakeId, PortalId> p_to_cake; // OK
p_to_cake[cake_id] = portal_id; // OK
p_to_cake[portal_id] = cake_id; // COMPILER ERROR
portal_id = cake_id; // COMPILER ERROR
portal_id = "1.0"; // COMPILER ERROR
portal_id = PortalId("42"); // OK
return 0;
}
We have already tried macros in combination with templates but didn't quite get what we needed. And to add - we CAN use c++17.
EDIT: The code we came up with was
#define SAFE_TYPEDEF(Base, name) \
class name : public Base { \
public: \
template <class... Args> \
explicit name (Args... args) : Base(args...) {} \
const Base& raw() const { return *this; } \
};
which is ugly and doesn't work. And by it doesn't work I mean that compiler was ok with .portal_id = cake_id;
EDIT2: Added explicit
keyword, with which our code actually works nicely for our example. Not sure though whether this is the right way to go and whether it covers all unfortunate situations.