Detect same class inheritance with SFINAE

2019-04-10 12:20发布

问题:

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.

回答1:

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, "!!");


回答2:

THE ERROR

prog.cpp: In instantiation of ‘struct fail<int, int>’: prog.cpp:23:29: required from here prog.cpp:9:8: error: duplicate base type ‘dummy<int>’ invalid struct fail : dummy<T>, dummy<U> {};

As stated in the above diagnostic you cannot explicitly inherit from the same base more than once in a base-specifier-list, and since T and U can possibly be of the same type.. BOOM.


WAIT, HOLD UP; WHAT ABOUT SFINAE?

SFINAE is only checked in the immediate context of the template itself, error that happens beyond the declaration are not suitable to trigger a SFINAE.

14.8.2p8 Template argument deduction [temp.deduct]

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguemts.

Only invalid types and expressions in the immediate context of the function type and its templat eparameter types can result in a deduction failure.

The ill-formed inheritance does not happen in the immediate context, and the application is ill-formed.

To answer your question explicitly: since inheritance never happens in the declaration of a certain function, the ill-formed inheritance itself cannot be caught by SFINAE.

Sure, you can ask for the compiler to generate a class that uses inheritance, by instantiation it in the function declaration, but the actual (ill-formed) inheritance is not in the immediate context.



回答3:

As I understand, you want a traits to check if all type are differents,
following may help:

#include <type_traits>

template <typename T, typename ...Ts> struct is_in;

template <typename T> struct is_in<T> : std::false_type {};
template <typename T1, typename T2, typename ... Ts>
struct is_in<T1, T2, Ts...> : std::conditional<std::is_same<T1, T2>::value, std::true_type, is_in<T1, Ts...>>::type {};


template <typename ... Ts> struct are_all_different;

template <> struct are_all_different<> : std::true_type {};
template <typename T> struct are_all_different<T> : std::true_type {};

template <typename T1, typename T2, typename ... Ts> struct are_all_different<T1, T2, Ts...> :
    std::conditional<is_in<T1, T2, Ts...>::value, std::false_type, are_all_different<T2, Ts...>>::type {};


static_assert(are_all_different<char, int, float, long, short>::value, "type should be all different");
static_assert(!are_all_different<char, int, float, char, short>::value, "type should not all different");


回答4:

What about a simple std::is_same<>? As far as I can see, it directly models the desired behaviour of your class.

So try something like this:

template<typename T, typename U>
fail
{
    fail(const T &t, const U &u)
    {
         static_assert(std::is_same<T,U>::value,"T and U must have a distinct type");
    }
};

Or even better, directly use std::is_same<T,U> in your code.

EDIT: Here is a solution inspired by Jarod42's but which uses only a single class (to make it clearer: this is an answer to the question how to write a variadic class template which detects whether all given types are distinct, which seemed to be the desired goal in one of the early versions of the original question):

#include <type_traits>

template <typename ...Ts> struct are_all_different {};
template <> struct are_all_different<> {static const bool value=true;};
template <typename T> struct are_all_different<T> {static const bool value=true;};

template <typename T1, typename T2>
struct are_all_different<T1, T2>
{
    static const bool value = !std::is_same<T1, T2>::value;
};

template <typename T1, typename T2, typename ...Ts>
struct are_all_different<T1,T2,Ts...>
{
    static const bool value = are_all_different<T1, T2>::value
                           && are_all_different<T1, Ts...>::value
                           && are_all_different<T2, Ts...>::value;
};

static_assert(are_all_different<char, int, float, long, short>::value, "type should be all different");
static_assert(!are_all_different<char, int, float, char, short>::value, "type should not all different");

In the end, for n variadic template arguments, this should check for equality of all of the n*(n+1)/2 combinations.



回答5:

I don’t really understand what you are trying to achieve, but I can help you with the error

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

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

The error is because when you instantiate fail with T and U the same you basically inherit from the same class twice, which is illegal, so you need to create a specialization to take care of this case.