Search code examples
c++c++11template-meta-programmingsfinaedecltype

Detect same class inheritance with SFINAE


I'm trying to write a metafunction that checks whether all types passed as a variadic template parameter are distinct. It seems that the most performant way to do this is to inherit from a set of classes and detect, whether there is an error.

The problem is that compilation fails in the following code, while I would expect SFINAE to work.

Edit. The question is not "how to write that metafunction" but "how do I catch that double inheritance error and output false_type when it happens". AFAIK, it's possible only with SFINAE.


template <typename T>
struct dummy {};

// error: duplicate base type ‘dummy<int>’ invalid
template <typename T, typename U>
struct fail : dummy<T>, dummy<U> {};

template <typename T>
true_type test(fail<T, T> a = fail<T, T>());

false_type test(...);

int main() {
    cout << decltype(test<int>())::value << endl;
}

Live version here.


Edit. Previously I've tried to do this with specialization failure, but it didn't work either with the same compilation error.

template <typename T>
struct dummy {};

template <typename T, typename U>
struct fail : dummy<T>, dummy<U>, true_type {};

template <typename T, typename U = void>
struct test : false_type {};

template <typename T>
struct test<T, typename enable_if<fail<T, T>::value, void>::type> : true_type {};

Live version here.


Solution

  • You can't catch duplicate inheritance with SFINAE, because it is not one of the listed reasons for deduction to fail under 14.8.2p8 [temp.deduct]; equally, it is because the error occurs outside the "immediate context" of template deduction, as it is an error with the instantiation of your struct fail.

    There is however a very similar technique which is suitable for use in your case, which is to detect an ambiguous conversion from a derived class to multiple base classes. Clearly the ambiguous base classes can't be inherited directly from a single derived class, but it works fine to inherit them in a linear chain:

    C<> A<int>
    |  /
    C<int> A<char>
    |     /
    C<char, int> A<int>
    |           /
    C<int, char, int>
    

    Now a conversion from C<int, char, int> to A<int> will be ambiguous, and as ambiguous conversion is listed under 14.8.2p8 we can use SFINAE to detect it:

    #include <type_traits>
    
    template<class> struct A {};
    template<class... Ts> struct C;
    template<> struct C<> {};
    template<class T, class... Ts> struct C<T, Ts...>: A<T>, C<Ts...> {};
    template<class... Ts> void f(A<Ts>...);
    template<class... Ts> std::false_type g(...);
    template<class... Ts> decltype(f((A<Ts>(), C<Ts...>())...), std::true_type()) g(int);
    template<class... Ts> using distinct = decltype(g<Ts...>(0));
    
    static_assert(distinct<int, char, float>::value, "!!");
    static_assert(!distinct<int, char, int>::value, "!!");