为什么SFINAE在这种情况下对我不正确以及如何解决?

时间:2017-09-08 15:19:02

标签: c++ templates c++14 sfinae

我试图留下struct A一个函数foo(打印0),如果它的参数有模板方法isA<void>而另一个(打印1)如果没有。这段代码(简化为下面的最小例子)编译(尝试使用gcc 6.1.0和clang-3.9.0以及显式--std=c++14选项)并运行。

但它打印1,但我确信它会打印0.我想知道我错在哪里,但真正的问题是:如何使这项工作正确?

请仅使用C ++ 14解决方案。

#include <type_traits>
#include <iostream>
#include <utility>

using std::enable_if;
using std::declval;
using std::true_type;
using std::false_type;
using std::cout;

template<int M>
struct ObjectX
{
  template<typename C>
  bool isA() { return false; }
};

struct XX : ObjectX<23456> {
  int af;
};

template <typename ObjType> using has_dep = decltype(declval<ObjType>().template isA<void>());

template <typename, typename = void>
struct has_isa : public false_type {};

template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {};

template<typename ObjType>
struct A
{
  template<typename T = void>
  typename enable_if<has_isa<ObjType>::value, T>::type
  foo() {
    cout << "called foo #0" << "\n";
  }

  template<typename T = void>
  typename enable_if<!has_isa<ObjType>::value, T>::type
  foo() {
    cout << "called foo #1" << "\n";
  }
};

int
main()
{
  A<XX> axx;
  // XX().template isA<void>(); -- to check, that we can call it and it exists
  axx.foo();
  return 0;
}

3 个答案:

答案 0 :(得分:4)

此计划存在两个问题。

首先,has_dep<XX>bool。当我们尝试has_dep<XX>时,添加默认模板参数意味着这实际上是has_dep<XX, void>。但专业化是has_dep<XX, bool> - 这与我们实际查找的内容不符。 boolvoid不符。这就是has_dep<XX>false_type的原因。对此的解决方案是std::void_t,我建议阅读该Q / A以了解其工作原理。在您的专业化中,您需要使用void_t<has_dep<ObjType>>代替。

其次,这是不对的:

template<typename T = void>
typename enable_if<has_isa<ObjType>::value, T>::type

SFINAE仅发生在替换的直接上下文中,并且类模板参数不在函数模板替换的直接上下文中。这里正确的模式是:

template <typename T = ObjType> // default to class template parameter
enable_if_t<has_isa<T>>         // use the function template parameter to SFINAE
foo() { ... }

进行这两个修复,程序按预期工作。

答案 1 :(得分:2)

你的sfinae失败了,因为has_isa选择了错误的专业化。

has_isa<T>的使用必须是默认实现或专用版本。

如您所定义,您有一个默认参数void:

//   default argument ---------v
template <typename, typename = void>
struct has_isa : public false_type {};

然后在表达式has_isa<T>中,第二个参数必须为void。它与撰写has_isa<T, void>大致相同。

问题在于:

template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType>> : public true_type {};
//                      ^--- what's that type?

即使模板部分排序会考虑这个&#34;重载&#34;更专业,它不会被选中。查看has_dep

的定义
struct XX {
  template<typename C> bool isA() { return false; }
};

template <typename ObjType>
using has_dep = decltype(declval<ObjType>().template isA<void>());

嘿,该类型has_dep<T>t.isA<void>()的返回类型,即bool

所以专业版看起来像这样:

template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType>> : public true_type {};
//                      ^--- really, this is bool in our case

因此,为了使其正常工作,您必须致电has_isa<T, bool>。由于这是不切实际的,您应该将您的专业化定义为:

template <typename ObjType>
struct has_isa<ObjType, void_t<has_dep<ObjType>>> : public true_type {};

void_t的定义如下:

template<typename...>
using void_t = void; // beware for msvc

因此,has_isa<T>将始终考虑专业化,因为我们发送void作为第二个模板参数,现在我们的专业化始终以void作为第二个参数。

另外,正如Barry所说,你的功能没有正确形成,因为sfinae只出现在紧急情况下。你应该这样写:

template<typename T = ObjType>
typename enable_if<has_isa<T>::value, void>::type
foo() { //                 ^--- sfinae happens with T
  cout << "called foo #0" << "\n";
}

如果您不希望公开模板参数,只需将该功能设为私有:

template<typename ObjType>
struct A {
public:
    void foo() {
        foo_impl();
    }

private:
    template<typename T = ObjType>
    typename enable_if<has_isa<T>::value, void>::type
    foo_impl() {
      cout << "called foo #0" << "\n";
    }

    template<typename T = ObjType>
    typename enable_if<!has_isa<T>::value, void>::type
    foo_impl() {
      cout << "called foo #1" << "\n";
    }
};

答案 2 :(得分:1)

你的问题是你专攻错误的课程:

您应该强制has_dep返回void

template <typename ObjType> using has_dep = decltype(static_cast<void>(declval<ObjType>().template isA<void>()));

所以这里

template <typename ObjType>
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {};
// It is really <bjType, void> you specialize.