SFINAE在返回类型中工作,但不作为模板参数

SFINAE working in return type but not as template parameter

本文关键字:不作为 参数 返回类型 工作 SFINAE      更新时间:2023-10-16

我已经使用了SFINAE习语很多次,我习惯了把我的std::enable_if<>放在模板参数中,而不是返回类型中。然而,我遇到了一些微不足道的情况,它没有工作,我不知道为什么。首先,这是我的main:

int main()
{
    foo(5);
    foo(3.4);
}
下面是触发错误的foo的实现:
template<typename T,
         typename = typename std::enable_if<std::is_integral<T>::value>::type>
auto foo(T)
    -> void
{
    std::cout << "I'm an integer!n";
}
template<typename T,
         typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
auto foo(T)
    -> void
{
    std::cout << "I'm a floating point number!n";
}

这里有一段类似的代码,可以正常工作:

template<typename T>
auto foo(T)
    -> typename std::enable_if<std::is_integral<T>::value>::type
{
    std::cout << "I'm an integrer!n";
}
template<typename T>
auto foo(T)
    -> typename std::enable_if<std::is_floating_point<T>::value>::type
{
    std::cout << "I'm a floating point number!n";
}

我的问题是:为什么foo的第一个实现触发错误,而第二个不触发它?

main.cpp:14:6: error: redefinition of 'template<class T, class> void foo(T)'
 auto foo(T)
      ^
main.cpp:6:6: note: 'template<class T, class> void foo(T)' previously declared here
 auto foo(T)
      ^
main.cpp: In function 'int main()':
main.cpp:23:12: error: no matching function for call to 'foo(double)'
     foo(3.4);
            ^
main.cpp:6:6: note: candidate: template<class T, class> void foo(T)
 auto foo(T)
      ^
main.cpp:6:6: note:   template argument deduction/substitution failed:
main.cpp:5:10: error: no type named 'type' in 'struct std::enable_if<false, void>'
          typename = typename std::enable_if<std::is_integral<T>::value>::type>
          ^

编辑:

工作代码和故障代码。

您应该看看14.5.6.1 Function template overloading (c++ 11标准),其中定义了函数模板等效性。简而言之,不考虑默认模板参数,因此在第一种情况下,您将定义两次相同的函数模板。在第二种情况下,表达式引用返回类型中使用的模板参数(再次参见14.5.6.1/4)。由于该表达式是签名的一部分,因此您将获得两个不同的函数模板声明,因此SFINAE有机会工作。

模板中的值:

template<typename T,
         typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
auto foo(T)
    -> void
{
    std::cout << "I'm an integer!n";
}
template<typename T,
         typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0>
auto foo(T)
    -> void
{
    std::cout << "I'm a floating point number!n";
}

模板的= ...只是给出了一个默认参数。这不是实际签名的一部分,看起来像

template<typename T, typename>
auto foo(T a);

用于函数。

根据您的需要,这个问题最通用的解决方案是使用标签调度。

struct integral_tag { typedef integral_tag category; };
struct floating_tag { typedef floating_tag category; };
template <typename T> struct foo_tag
: std::conditional<std::is_integral<T>::value, integral_tag,
                    typename std::conditional<std::is_floating_point<T>::value, floating_tag,
                                               std::false_type>::type>::type {};
template<typename T>
T foo_impl(T a, integral_tag) { return a; }
template<typename T>
T foo_impl(T a, floating_tag) { return a; }
template <typename T>
T foo(T a)
{
  static_assert(!std::is_base_of<std::false_type, foo_tag<T> >::value,
                 "T must be either floating point or integral");
  return foo_impl(a, typename foo_tag<T>::category{});
}
struct bigint {};
template<> struct foo_tag<bigint> : integral_tag {};
int main()
{
  //foo("x"); // produces a nice error message
  foo(1);
  foo(1.5);
  foo(bigint{});
}