For a simple compile time error if any type in the variadic parameter pack is duplicated, it's pretty simple:
template <typename T> struct Base{};
template <typename... Ts> struct NoDuplicates : Base<Ts>... {
constexpr operator bool() const { return true; }
};
That's it, and if it's what you need it will compile faster than any recursive template metaprogramming, fold expression, or type-trait approach. In fact, I know of no faster technique at compile time.
This works because a class is not allowed to inherit from the same base class twice. The reason it inherits from Base<T>
instead of just T
, is in case T is a type you can't inherit from, such as a primitive integral value, or an array, or void, etc.
To use:
template <typename... Ts>
class Foo {
static_assert(NoDuplicates<Ts...>{});
};
Foo<int, char, int> foo; // ERROR (see below)
<source>:3:34: error: duplicate base type 'Base<int>' invalid
3 | template <typename... Ts> struct NoDuplicates : Base<Ts>... {
Now, if you don't want a compile error, but want to compute a boolean indicating if there are any duplicates, it's a little more complicated, but not too bad. Comments show the 3 cases to check:
template <typename T, typename... Rest>
constexpr bool hasDuplicates() {
// Check T against each item in Rest, and if any match we have duplicates
if ((std::is_same_v<T, Rest> || ...))
return true;
// Is there anything left to check in Rest? If not, no duplicates.
if constexpr (sizeof...(Rest) == 0)
return false;
// T isn't duplicated, but is anything in Rest duplicated?
return hasDuplicates<Rest...>();
}
It can be used similarly:
template <typename... Ts>
class Foo {
static_assert(not hasDuplicates<Ts...>());
};
Foo<int, std::string, std::string> foo; // Error, there are dupes
And finally, if you only care if a specific type is duplicated, it is even easier:
// Check if Needle is found 2 or more times in this Haystack
template <typename Needle, typename... Haystack>
constexpr bool hasDuplicateInList() {
return ((std::is_same_v<Needle, Haystack> + ...)) > 1;
}
As far as "throwing" goes, if that's what you want, you can always throw an exception if you detect the boolean having a disallowed value in a normal if