【问题标题】:SFINAE working in return type but not as template parameterSFINAE 以返回类型工作,但不作为模板参数
【发布时间】:2013-03-03 21:18:30
【问题描述】:

我已经多次使用 SFINAE 成语,并且习惯将 std::enable_if<> 放在模板参数中,而不是返回类型中。但是,我遇到了一些它不起作用的琐碎案例,我不知道为什么。首先,这是我的主要内容:

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>
          ^

编辑

Working codefaulty code

【问题讨论】:

标签: c++ templates c++11 sfinae


【解决方案1】:

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

【讨论】:

  • 非常感谢。这个解释至少简单明了。我不知道这个规则:)
  • 我喜欢这句话:“不考虑默认模板参数”open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4205.pdf 的第 34 页中找不到任何单词 default(规则 14.5.6.1)。不过,“en.cppreference.com/w/cpp/language/…”中有这样的声明。你介意告诉我解释是从哪里来的吗?谢谢。
  • @cppBeginner 好吧,我猜它或多或少,正是因为在等效定义中没有引用默认参数。不过,所有这些都来自 C++11 标准,而不是某种“参考”站点。如果您对 C++14 感兴趣,我相信至少看一下最终草案(也是免费的)会更好,而不是缺少许多相关部分的提案。
【解决方案2】:

模板中的值起作用:

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";
}

【讨论】:

  • 额外:typename = typename... 表示默认参数,并且只能出现一次。而如果替换为typename...,则表示为SFINAE
【解决方案3】:

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

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{});
}

【讨论】:

  • 这不是很通用 - 它特定于这个确切的情况 - 整数与浮点数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-29
  • 2021-12-17
  • 2020-03-09
  • 1970-01-01
相关资源
最近更新 更多