【问题标题】:Simple SFINAE Problem conditionally declaring member function有条件地声明成员函数的简单 SFINAE 问题
【发布时间】:2020-08-04 12:50:20
【问题描述】:

我已阅读以下主题:

no type named ‘type’ in ‘struct std::enable_if<false, void>

Selecting a member function using different enable_if conditions

"What happened to my SFINAE" redux: conditional template class members?

但是,我似乎无法在 gcc 和 msvc 上解决这个相当简单的 SFINAE 问题:

#include <type_traits>
#include <iostream>

template<typename A, typename B>
class Test {
public:

  template<typename X=A, typename = typename std::enable_if<std::is_same<X, void>::value, void>::type >
  void foo() {
    std::cout << "A";
  }

  template<typename X=A, typename = typename std::enable_if<!std::is_same<X, void>::value, void>::type >
  void foo() {
    std::cout << "B";
  }


};

int main(int argc, char **argv) {

  Test<int, float> t;

  t.foo();

  return 0;
}

实际结果:

A = void:完全错误:

main.cpp:15:8: error: 'template<class A, class B> template<class X, class> void Test<A, B>::foo()' cannot be overloaded with 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
   15 |   void foo() {
      |        ^~~
main.cpp:10:8: note: previous declaration 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
   10 |   void foo() {
      |        ^~~

A = int:完全错误:

main.cpp:15:8: error: 'template<class A, class B> template<class X, class> void Test<A, B>::foo()' cannot be overloaded with 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
   15 |   void foo() {
      |        ^~~

main.cpp:10:8: note: previous declaration 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
   10 |   void foo() {
      |        ^~~

main.cpp: In function 'int main(int, char**)':

main.cpp:26:9: error: no matching function for call to 'Test<int, float>::foo()'
   26 |   t.foo();
      |         ^

main.cpp:10:8: note: candidate: 'template<class X, class> void Test<A, B>::foo() [with X = X; <template-parameter-2-2> = <template-parameter-1-2>; A = int; B = float]'
   10 |   void foo() {
      |        ^~~

main.cpp:10:8: note:   template argument deduction/substitution failed:

main.cpp:9:26: error: no type named 'type' in 'struct std::enable_if<false, void>'
    9 |   template<typename X=A, typename = typename std::enable_if<std::is_same<X, void>::value, void>::type >
      |                          ^~~~~~~~

预期结果

A = void:输出“A”

A = int:输出“B”

我想要的是基于模板参数实现不同的(附加)成员函数。 但是,似乎我无法使 enable_if 依赖于类模板类型,但我不知道为什么。根据链接的线程,上面的代码显示是正确的。 你能解释一下为什么这不起作用吗?

Live Link

【问题讨论】:

  • 您跳过了编译器错误的重要部分。最好在问题中包含完整的错误
  • 我已经添加了(我认为)初始错误,因为输出会非常糟糕。
  • 但您在问题中包含的错误只是实际错误(您未包含)的结果
  • 好的,添加了完整的编译输出。抱歉,我认为包含链接会更好
  • 别担心,添加一个在线编译器的链接很好,但不是更好;)

标签: c++ sfinae


【解决方案1】:

cppreference 的注释显示类似并解释了它为什么不起作用:

一个常见的错误是声明两个仅在默认模板参数上有所不同的函数模板。这不起作用,因为声明被视为同一函数模板的重新声明(默认模板参数不计入函数模板等效项)。

/* WRONG */

struct T {
    enum { int_t,float_t } m_type;
    template <typename Integer,
              typename = std::enable_if_t<std::is_integral<Integer>::value>
    >
    T(Integer) : m_type(int_t) {}

    template <typename Floating,
              typename = std::enable_if_t<std::is_floating_point<Floating>::value>
    >
    T(Floating) : m_type(float_t) {} // error: treated as redefinition
};

/* RIGHT */

struct T {
    enum { int_t,float_t } m_type;
    template <typename Integer,
              std::enable_if_t<std::is_integral<Integer>::value, int> = 0
    >
    T(Integer) : m_type(int_t) {}

    template <typename Floating,
              std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
    >
    T(Floating) : m_type(float_t) {} // OK
};

对您的代码应用相同的修复,使其输出所需的B

#include <type_traits>
#include <iostream>

template<typename A, typename B>
class Test {
public:

  template<typename X = A,std::enable_if_t<std::is_same<X, void>::value, int> = 0>
  void foo() {
    std::cout << "A";
  }

  template<typename X=A,std::enable_if_t<!std::is_same<X, void>::value, int> = 0>
  void foo() {
    std::cout << "B";
  }


};

在您的代码中,两个函数模板的不同之处仅在于它们的默认参数。修复后的第二个参数是int = 0 或替换失败。

【讨论】:

  • 啊,我读到了,不知道应该怎么做,但现在我明白了,因为类型 void = 0 会失败!现在说得通了,谢谢!!
  • @namezero 不,它不是void = 0std::enable_if&lt;true,int&gt; 有一个::type = intstd::enable_if&lt;false,int&gt; 没有它。不用担心,即使经过多次尝试,我仍然对 SFINAE 感到困惑。当我看到它时,我可以理解代码,但不知道如何到达那里。我很幸运能找到那张纸条;)
  • 是的,所以一个是未命名的 int 类型,默认 = 0,另一个失败,这就是我认为它被丢弃的原因。将笔记丢弃为“不明白”让我感到羞耻:D
  • @namezero 不,它不是未命名的类型。就像你有一个模板template &lt;typename T&gt; struct foo {},而在第二个模板中你使用foo&lt;S&gt;::type。这是替换失败
【解决方案2】:

这是一个 C++17 版本:

template<typename X=A>
std::enable_if_t<std::is_same_v<X, void>> foo() {
    std::cout << "A";
}

template<typename X=A>
std::enable_if_t<!std::is_same_v<X, void>> foo() {
    std::cout << "B";
}

enable_if 的默认类型是 void,用作函数的类型)

您也可以使用constexpr if

void foo() {
    if constexpr (std::is_same_v<A, void>) {
        std::cout << "A";      
    } else {
        std::cout << "B";
    }
}

C++11:

template<typename X=A>
typename std::enable_if<std::is_same<X, void>::value>::type foo() {
    std::cout << "A";
}

template<typename X=A>
typename std::enable_if<!std::is_same<X, void>::value>::type foo() {
    std::cout << "B";
}

【讨论】:

  • 感谢17版。这在将来非常有用;正在使用的库虽然还不支持它。 C++11 版本和我差不多,除了类型名!
  • @namezero 欢迎您!是的,它们非常相似。我将 sfinae 部分移至返回类型,可以说是用一块石头杀死两只鸟 :)
猜你喜欢
  • 1970-01-01
  • 2017-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多