Let's say I have an alias:
using bar = foo<string, string, int>;
How can I make sure that "string" only appear once in the parameter? If it appears more than once then throw an error.
I have made a function to count how many times a type appeared in the parameter but failed to implement the idea.
template <class T>
constexpr std::size_t type_count_impl(std::size_t count = 0) {
return count;
};
template <class T, class T1, class... Types>
constexpr std::size_t type_count_impl(std::size_t count = 0) {
return type_count_impl<T, Types...>(count (std::is_same<T, T1>::value ? 1 : 0));
};
template <class T, class... Types>
constexpr std::size_t type_count() {
return type_count_impl<T, Types...>();
};
CodePudding user response:
How can I make sure that "
std::string
" only appear once in the parameter? If it appears more than once then throw an error.
You do not need to count the types, rather you can write a trait to check whether all the template parameters of a class template are same as follows (Required c 17 or later):
#include <type_traits> // std::is_same, std::conjunction_v, std::negation
// template parameter types checking
template<typename Type, typename... Rs>
inline constexpr bool areUniqueTypes = std::conjunction_v<std::negation<std::is_same<Type, Rs>>...>;
template <typename T> struct unique_types final : std::false_type {};
template <template<typename...> class ClassType, typename... Types>
struct unique_types<ClassType<Types...>> final
{
inline static constexpr bool value = areUniqueTypes<Types...>;
};
// traits helper
template<typename ClassType>
inline constexpr bool unique_types_v = unique_types<ClassType>::value;
CodePudding user response:
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
CodePudding user response:
Your type_count_impl()
does not need to accept an argument, just simply return 0
when the size of Types...
is 0
. So your type_count()
can be simplified to:
#include <type_traits>
template <class T>
constexpr std::size_t type_count() {
return 0;
};
template <class T, class T1, class... Types>
constexpr std::size_t type_count() {
return type_count<T, Types...>() std::is_same<T, T1>::value;
};
#include <string>
static_assert(type_count<std::string, std::string, std::string, int>() == 2);
static_assert(type_count<int, std::string, std::string, int>() == 1);
static_assert(type_count<int>() == 0);