g++和clang++在SFINAE和SFINAE失效时的不同行为

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

本文关键字:SFINAE 失效 clang++ g++      更新时间:2023-10-16

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 intn"; }
      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>类,该类(通过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++还是叮当?

关于g++生成的程序输出,它是以下

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

因此g++似乎忽略了CCD_ 3指令。

现在有一点变化:我用这种方式修改了foo<X>::bar()的第二个版本

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

删除函数obiot内的CCD_ 5。现在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 intn";
}
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