可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.