Question

I have this code, but it does not compile:

#include <iostream>
#include <stdexcept>
#include <cassert>


template< class > struct id{};
template< class U, class V> struct base: public id<U>, public id<V>
{
    static const bool value = true;
};

template< class U, class V>
struct is_different
{

  typedef char (&true_)[1];
  typedef char (&false_)[2];

  template< class T, class K, bool  = base<T,K>::value >
  struct checker;



  template< class T, class K>
  static true_  test( checker<T,K>* );

  template< class , class >
  static false_  test(...);


  static const bool value = sizeof( test<U,V>(0) ) == sizeof(true_);

};


int main (void) 
{
    bool b1 = is_different<int,float>::value;
    bool b2 = is_different<int,int>::value; // <--- error

    std::cout << std::boolalpha << b1 << '\n'
                                << b2  << '\n';
    return 0;       
}

error:

main.cpp: In instantiation of ‘struct base<int, int>’:
main.cpp:25:17:   required by substitution of ‘template<class T, class K> static char (& is_different<U, V>::test(is_different<U, V>::checker<T, K>*))[1] [with T = T; K = K; U = int; V = int] [with T = int; K = int]’
main.cpp:31:41:   required from ‘const bool is_different<int, int>::value’
main.cpp:39:38:   required from here
main.cpp:7:36: error: duplicate base type ‘id<int>’ invalid
 template< class U, class V> struct base: public id<U>, public id<V>

Why I can't use duplicate inheritance's failure as SFINAE?

Was it helpful?

Solution

SFINAE only applies to things directly within the function signature. The compilation error you get happens within the instantiation of base<T, K>, which is two steps removed from the function signature.

That said, why do you do this the hard way?

template <typename T, typename U>
struct is_same { static const bool value = false; };

template <typename T>
struct is_same<T, T> { static const bool value = true; };

template <typename T, typename U>
struct is_different { static const bool value = !is_same<T, U>::value; };

Edit:

So, to answer your question in the comment, let me explain a bit more about SFINAE. The standard specifies SFINAE in 17.8.2p8:

If a substitution results in an invalid type or rexpression, type deduction fails. An invalid type or rexpression is one that would be ill-formed if written using the substituted arguments. Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of the class template specializations and/or function template specializations, the generation of implicitly-defined functions, et. Such side effects are not in the "immediate context" and can result in the program being ill-formed. - end note]

The thing that's problematic here is the "immediate context". You are doing two things here that remove your error from the immediate context.

  • The error occurs during evaluation of the default argument expression of checker. Although this evaluation is triggered by the occurrence of checker<T,K> in the argument list of test, which gets instantiated for overload resolution, it isn't the immediate context. You can try this out by changing the definition of base to template <typename T, typename U> struct base {};. This makes the error in the instantiation of base go away, replacing it with an error in the context of the default argument expression ("does not have a member called value"). But SFINAE will still not trigger; you'll still get a hard error.
  • The error occurs in the instantiation of the definition of base. This is yet another step removed from overload resolution. For the same reason, putting a member into base with type T::foobar doesn't work for finding out whether T contains a type foobar: the definition of base is not a SFINAE context anymore. You can see that this alone is an error by removing checker from the picture, e.g. doing this:

 

template <typename U, typename V> struct base: public id<U>, public id<V> {
    typedef void type;
};
template <typename U, typename V> struct is_different {
  struct true_ { char c[1]; };  // I don't like sizeof(reference type),
  struct false_ { char c[2]; }; // makes me nervous.
  template <typename T, typename K>
  static true_ test(typename base<T, K>::type*);
  template <typename, typename>
  static false_ test(...);

  static const bool value = sizeof(test<U, V>(0)) == sizeof(true_);
};

Now the base instantiation doesn't hide behind a template default argument expression, but is right there in the signature, but you'll still get an error.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top