template<typename T>
concept HasId =
requires (T a) {
T::kId; // "the expression T::kId is a valid expression that will compile"
};
// A and B are expected to be structs, where each has an associated integer constant kId and a field "int value".
template <HasId A, HasId B>
int min_id(const A a, const B b) {
if (A::kId < B::kIs) {
return a.value;
} else {
return b.value;
}
}
struct A0 {
static constexpr int kId = 1;
int value;
};
struct A1 {
static constexpr int kId = 2;
int value;
};
// etc.
struct A180 {
static constexpr int kId = 2;
int value;
};
int main() {
(void)min_id(A0(), A1());
(void)min_id(A1(), A2());
// etc. ending with:
(void)min_id(A179(), A180());
}
And now you get 180 errors about your typo (if not for max error number limits in the compiler), when really there is only one error in the definition of min_id. I used concepts to show that in the long tradition of C++, they're an additional feature that doesn't really help (quantity over quality of features).Haskell solves that with typeclasses, Rust solves that with traits. What's really important about these solutions is that when you write a function generic over a trait or a typeclass, you are limited to using what is defined in the trait/typeclass. The trait/typeclass function both as documentation and as something that gets you better compilation errors.
"typename" in C++ is basically "any", but for values of template execution. Typeclasses/traits on the other hand are a sort of types for those values (which we normally call "types"). And similarly, in Zig there is an "any" type for types, just like in C++ --- it's called "type", and AFAIK there is no way to constrain your types with types of types.