g++ and clang++ different behaviour with SFINAE an

2019-06-20 04:25发布

A couple of questions for C++11 experts.

I'm fighting with SFINAE and I came across a strange case in which g++ (4.9.2), and clang++ (3.5.0) behave differently.

I have prepared the following sample code. I'm sorry but I'm unable to do it significantly more concise.

#include <string>
#include <iostream>
#include <typeinfo>
#include <type_traits>

template <typename X>
class foo
 {
   private:
      template <typename R>
         using enableIfIsInt
         = typename std::enable_if<std::is_same<X, int>::value, R>::type;

   public:
      foo ()
       { }

      template <typename R = void>
         enableIfIsInt<R> bar ()
          { std::cout << "bar: is int\n"; }

      void bar ()
       {
         std::cout << "bar: isn't int; is [" << typeid(X).name() << "]{"
            << typeid(enableIfIsInt<void>).name() << "}\n";
       }
 };


int main ()
 {
   foo<long>  fl;
   foo<int>  fi;

   fl.bar();
   fi.bar();

   return 0;
 }

My idea was to create a template foo<X> class that (via SFINAE) can define a method in one or in another way depending on the X template argument.

The program compile well with g++ 4.9.2 but clang++ 3.5.0 give the following error

test.cpp:13:36: error: no type named 'type' in
      'std::__1::enable_if<false, void>'; 'enable_if' cannot be used to disable
      this declaration
         = typename std::enable_if<std::is_same<X, int>::value, R>::type;
                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:26:23: note: in instantiation of template type
      alias 'enableIfIsInt' requested here
            << typeid(enableIfIsInt<void>).name() << "}\n";
                      ^
test.cpp:36:7: note: in instantiation of member function
      'foo<long>::bar' requested here
   fl.bar();
      ^
1 error generated.

I suppose that is right clang++ but my first question to C++11 experts is: who right? g++ or clang++?

About the g++ produced program output, it's the following

bar: isn't int; is [i]{v}

so g++ seems to ignore the fl.bar(); instruction.

Now a little change: i modify the second version of foo<X>::bar() in this way

  void bar ()
   { std::cout << "bar: isn't int; is [" << typeid(X).name() << "]\n"; }

deleting the std::enable_if inside the function abomination. Now both g++ and clang++ are compiling without problems and the output, for both compiled versions of the program, is

bar: isn't int; is [l]
bar: isn't int; is [i]

So, my second question is: what I'm doing wrong? Why, in the int case, I don't obtain the "is int" version of foo<X>::bar()?

Be patient with me if I'm doing some foolish: I'm trying to learn C++11.

And sorry for my bad English.

1条回答
Rolldiameter
2楼-- · 2019-06-20 04:35

clang's error isn't coming from the substitution failure. It's coming from here:

  void bar ()
   {
     std::cout << "bar: isn't int; is [" << typeid(X).name() << "]{"
        << typeid(enableIfIsInt<void>).name() << "}\n"; // <==
   }

enableIfIsInt<void> isn't in the immediate context, that's a hard failure for X is not int. You simply can't use that expression in that context.

Once you remove that - the non-template bar() is always called. That's because both functions are equivalent matches and non-templates are preferred to templates in overload resolution.

So the real solution is to use tag-dispatching:

void bar() { bar(std::is_same<X, int>{}); }

void bar(std::true_type ) {
    std::cout << "bar: is int\n";
}

void bar(std::false_type ) {
    std::cout << "bar: isn't int; is [" << typeid(X).name() << "]\n";
}

with which both compilers happily yield:

bar: isn't int; is [l]
bar: is int
查看更多
登录 后发表回答