Home > Back-end >  How can I make sure a type only appear once in a template parameter?
How can I make sure a type only appear once in a template parameter?

Time:11-12

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 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;

(See Live Online)

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);

Demo.

  • Related