在这种情况下,为什么Sfinae对我来说不正确以及如何修复它

Why SFINAE works incorrect for me in this case and how to fix it?

本文关键字:何修复 不正确 对我来说 这种情况下 为什么 Sfinae      更新时间:2023-10-16

我试图在 struct A中留下一个函数 foo(prints 0),如果其参数具有模板方法 isA<void>和另一个函数(如果没有)。此代码(以下最小示例简化为最小示例)编译(使用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;
}

此程序中有两个问题。


首先,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() { ... }

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

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

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

按照您的定义,您有一个默认参数可以void:

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

然后在表达式has_isa<T>中,第二个参数必须无效。大致与编写has_isa<T, void>

问题是:

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

即使模板部分订购会考虑此"超载"更专业,但不会选择它。查看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";
    }
};

您的问题是您专业的类别:

您应该强迫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.