4

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.

polkovnikov.ph
  • 6,256
  • 6
  • 44
  • 79

5 Answers5

6

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, "!!");
ecatmur
  • 152,476
  • 27
  • 293
  • 366
4

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.

Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • @polkovnikov.ph that was not stated in your question, the main objective of this post is to answer *Why?*, the implementation is a bonus. Let me write an optimized version for you, just to show that that too is possible. But as said; **Why?** has been answered. – Filip Roséen - refp Jun 05 '14 at 08:55
  • @polkovnikov.ph I've explicitly answered your question, an invalid *base-specifier-list* cannot be instantiated and caught using *SFINAE*. You'll need to first check if such inheritance is possible, and if so; either instantiate it, or not. – Filip Roséen - refp Jun 05 '14 at 09:18
  • Oh, now I see. Stupid me. – polkovnikov.ph Jun 05 '14 at 09:19
1

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.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
1

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");
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 3
    @Filip Roséen: Man, answers might be either some explanantions which help nothing (see your post) or solutions like this one (which might also include hints or best practices the OP hasn't thought about). I like better the solutions. – davidhigh Jun 05 '14 at 08:16
  • 3
    @davidhigh: providing work-arounds without explaining why the particular approach attempted did not work is helpful in the short-term, but not in the long term. *Give a man a fish...* – Matthieu M. Jun 05 '14 at 08:33
  • @FilipRoséen-refp: That being said, and while you are free to vote at your own volition, be aware that a number of people consider down-votes as aggressive (for better or worse), and therefore down-voting useful (if incomplete) answers you will be perceived as aggressive. My advice would be to comment on what you perceive as incomplete answers without down-voting; it's up to you to follow it or not :) – Matthieu M. Jun 05 '14 at 08:41
  • I agree that I don't (fully) answer the OP question and explanation about *immediate context* is missing (but I find it rude to down-vote for that). – Jarod42 Jun 05 '14 at 08:42
  • Ok, I agree as well with all of you: having both in your post, the specific answer and the possible improvements is surely the best option. So let's all be friends :-D – davidhigh Jun 05 '14 at 08:45
  • @davidhigh I'm aware about recursive metafunction implementation, but variadic templates were added to speed up metacomputations, and I would like to leave recursion for C++03. – polkovnikov.ph Jun 05 '14 at 08:55
  • @polkovnikov.ph I don't fully understand what you are meaning as you were the one to bring in variadic templates -- an exclusive C++11 feature -- in your original question ... so please explain. – davidhigh Jun 05 '14 at 09:09
  • @davidhigh Nevermind, @FilipRoséen just've explained why the `O(1)` amortized instantiations solution is impossible. – polkovnikov.ph Jun 05 '14 at 10:13
  • @FilipRoséen-refp: removing all of your comments renders the whole comment list incoherent if not almost useless. Is that really necessary? – davidhigh Jun 06 '14 at 11:40
0

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.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • OP wants a type traits `are_all_different` – Jarod42 Jun 05 '14 at 07:35
  • @Jarod42 I got that, I just don’t understand how all this is going to help him. I would go a whole different way to achieve that. But if he can make this work it doesn’t matter anything else. – bolov Jun 05 '14 at 07:39
  • Didn't I say that I know the source of the error? Doesn't it look like an error was added there intentionally? – polkovnikov.ph Jun 05 '14 at 08:56
  • @polkovnikov.ph where did you say you know the error? To me it doesn’t look like the error was added there intentionally. Maybe I don’t understand your post, in which case I am sorry. Quote from your question: `The problem is that compilation fails in the following code, while I would expect SFINAE to work.` – bolov Jun 05 '14 at 09:02
  • @bolov I've fixed the question to be more specific. Sorry. – polkovnikov.ph Jun 05 '14 at 09:05