Table of Contents
The code using SomeType = int32_t; gets you just an alias, not a separate type, no type safety.
Some are proposing general purpose, feature-loaded libraries providing a machinery entered like Strong<int32_t>to solve this. But this pulls in a dependency and bloats syntax quite a bit.
What is needed, as mundane it may sound, is just a C&P snippet for the most minimalist use-case. And here it is:
struct SomeType {
int32_t value_{};
explicit constexpr SomeType(int v) : value_{v} {}
friend constexpr bool operator==(const SomeType&, const SomeType&) = default;
};
Extend when needed, drop things very seldom.
This (and also when dropping the member initializer) satisfies just the most basic requirement, which is regularity. In the precise sense:
inline /*aka the chosen one*/ namespace more_generic {
/** Opposed to std::regular this doesn't contain std::default_initializable.
The intention of the latter might be to be able to write something like 'R x;' with possibly uninitialized x,
perhaps modelling a mathematical 'let x be an arbitrary element of R'. But to be honest, that's not needed in
programming. You are more likely to have a function implementation of 'f(R x)' (or 'f(Regular auto x)') getting in
an arbitrary x. Also it is the preferable principle of generalization to impose less constraints. You can still
have it for your specific type R if you want to. Above all it is just not helping to be forced to have a default
constructor, quite on the contrary, so std::regular is not a good choice.
Semantics:
* T a{b} => (b==c => a==c)
* a=b => (b==c => a==c)
* f FunctionalProcedure and a==b => f(a)==f(b)
Time/space complexity:
* each operation on Regular is no worse than linear in the memory of the object
Note: Right now it is highly improbable, that we also add total ordering and underlying type here, as proposed by
the master (Stepanov) at some time. That's again too much restriction (which he himself at another place discourages
us from rightfully. We will provide separate concepts.*/
template <typename R>
concept Regular = std::copyable<R> && std::equality_comparable<R> && requires(R a, R b, R c) {
// would be too verbose / duplicated to have the following here (already part of the concepts built upon)
// R{};
// R{a};
// a == a;
// c = a;
/*semantics*/ {
c = a, c == a;
!(a == b) || (!(b == c) || a == c);
};
};
} // namespace more_generic
namespace generic {
/** Opposed to Regular from more_generic this here implies default initializable also. This allows for the equivalence
of `T a; a = b;` and `T a{b};`. (`T a;` possibly uninitialized despite default constructed.) Sounds better at first,
but read the comment at the actually chosen version of Regular.*/
template <typename R>
concept Regular = std::regular<R>;
} // namespace generic