Search code examples
c++overloadingvariadic-templatesmultiple-inheritancec++20

Compile-time C++ function to check whether all template argument types are unique


There is a nice question (Which substitution failures are not allowed in requires clauses?) proposing the next problem.

One needs to write a compile-time function template<typename... Ts> constexpr bool allTypesUnique() that will return true if all argument types are unique, and false otherwise. And the restriction is not to compare the argument types pairwise. Unfortunately, the answer only explains why such function cannot be implemented with some particular approach.

I think the solution can be achieved using multiple inheritance. The idea is to make a class inherited from a number of classes: one for each type T in Ts. And each such class defines a virtual function with a signature depending on T. If some T is found more than once in Ts then function f in a child class will override the function in a base class and it can be detected:

template<typename> struct A{};

template<typename T, typename... Ts>
struct B : B<Ts...> {
    using B<Ts...>::f;
    constexpr virtual void f(A<T>, bool & unique) { if( ++count > 1 ) unique = false; }
    int count = 0;
};

template<typename T>
struct B<T> {
    constexpr virtual void f(A<T>, bool & unique) { if( ++count > 1 ) unique = false; }
    int count = 0;
};

template<typename... Ts>
constexpr bool allTypesUnique() {
    B<Ts...> b;
    bool res = true;
    ( b.f( A<Ts>{}, res ), ... );
    return res;
}

int main() {
    static_assert( allTypesUnique<void>() );
    static_assert( allTypesUnique<void, int&>() );
    static_assert( !allTypesUnique<int&, int&>() );
    static_assert( allTypesUnique<char, short, int>() );
    static_assert( !allTypesUnique<char, short, char>() );
}

Demo: https://gcc.godbolt.org/z/8jhnE7P11

Just curious, is the solution correct and is there a simpler solution for this problem?


Solution

  • If you use virtual base classes depending on each of the given types, you will get exact one base class instance for every unique type in the resulting class. If the number of given types is the number of generated base classes, each type was unique. You can "measure" the number of generated base classes by its size but must take care that you have a vtable pointer inside which size is implementation dependent. As this, each generated type should be big enough to hide alignment problems.

    BTW: It works also for reference types.

    
    template < typename T> struct AnyT { char i[128]; };
    
    template < typename FIRST, typename ... T>
    struct CheckT: virtual AnyT<FIRST>, virtual CheckT<T...> { };
    
    template < typename FIRST >
    struct CheckT<FIRST>: virtual AnyT<FIRST> {};
    
    
    template < typename ... T>
    constexpr bool allTypesUnique()
    {
        using T1 = CheckT<int>;
        using T2 = CheckT<bool, int>;
    
        constexpr std::size_t s1 = sizeof( T1 );
        constexpr std::size_t s2 = sizeof( T2 );
        constexpr std::size_t diff = s2 - s1; 
        constexpr std::size_t base = s1 - diff;
        constexpr std::size_t measure = sizeof( CheckT< T...> );
    
        return !((sizeof...(T)*diff+base) - measure);
    }
    
    
    int main() {
        static_assert( allTypesUnique<void>() );
        static_assert( allTypesUnique<void, int>() );
        static_assert( !allTypesUnique<void, void>() );
        static_assert( allTypesUnique<char, short, int>() );
        static_assert( !allTypesUnique<char, short, char>() );
    }
    

    Demo