g++ 和 clang++ SFINAE 和 SFINAE 失败的不同行为

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

C++11 专家的几个问题。

我正在与 SFINAE 打交道,我遇到了一个奇怪的情况,其中 g++ (4.9.2) 和 clang++ (3.5.0) 的行为不同。

我准备了以下示例代码。很抱歉,我无法做到更简洁。

#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;
 }

我的想法是创建一个模板 foo<X> class,它(通过 SFINAE)可以根据 X 模板参数以一种或另一种方式定义方法。

程序在 g++ 4.9.2 下编译良好,但在 clang++ 3.5.0 下出现以下错误

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.

我想 clang++ 是对的,但我对 C++11 专家的第一个问题是:谁是对的? g++ 还是 clang++?

关于g++产生的程序输出,如下

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

所以 g++ 似乎忽略了 fl.bar(); 指令。

现在稍微改动一下:我把foo<X>::bar()的第二个版本修改成这样

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

删除std::enable_if里面的函数可恶。现在 g++ 和 clang++ 都可以毫无问题地编译,并且程序的两个编译版本的输出都是

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

所以,我的第二个问题是:我做错了什么?为什么在 int 的情况下,我没有获得 foo<X>::bar()"is int" 版本?

如果我在做一些傻事,请耐心等待:我正在努力学习 C++11。

抱歉我的英语不好。

clang 的错误不是来自替换失败。它来自这里:

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

enableIfIsInt<void> 不在直接上下文中,这是一个严重的失败,因为 X 不是 int。您根本不能在该上下文中使用该表达式。

一旦你删除它 - 非模板 bar() 总是被调用。那是因为这两个函数是等价匹配,并且在重载决策中非模板优先于模板。

所以真正的解决方案是使用标签调度:

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";
}

两个编译器都愉快地产生:

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