【问题标题】:Using SFINAE in C++11 to choose between two functions with same signature在 C++11 中使用 SFINAE 在具有相同签名的两个函数之间进行选择
【发布时间】:2016-02-14 20:42:52
【问题描述】:

我正在尝试在 c++11 中使用 SFINAE 在具有相同签名但不同主体的两个函数之间进行选择,具体取决于在给定的 T 类型内是否应该有 typedef 的事实。例如,我有这两个结构,一个定义了 MyTypedef,一个什么都没有:

struct MyStruct1
{
    typedef std::false_type MyTypedef;

    ...
}

struct MyStruct3
{
    ...
}

然后我有两个模板函数,它们应该将结构的类型作为 T 并根据 MyTypedef 的存在做不同的事情。特别是,如果定义了T::MyTypedef,我应该调用第一个函数,在所有其他情况下我应该调用第二个函数。我尝试过这样的事情:

template<typename T>
void myFunction()
{
    ...
}

template<typename T, typename std::enable_if<T::MyTypedef>::type* = nullptr>
typename myFunction()
{
    ...
}

但它不能编译,错误是 C2668 (MSVC) - 'function' : ambiguous call to overloaded function

【问题讨论】:

  • C++0x 不再存在,它是 C++11 的草稿名称
  • 您还必须禁用第一个重载,当启用第二个时,反之亦然
  • 我通过删除问题不需要的案例来简化示例代码。您能否解释一下如何禁用第一个重载并仅在定义 T::MyTypedef 时启用第二个?
  • 另外,typedef 是一种糟糕的、老式的别名定义方式,如果可能的话,我建议您开始使用using
  • @Stefano:如果您想在这样的重载集上使用 SFINAE,那么绝对不能出现模板参数可能导致两个重载都可见的情况。在您的情况下,您的第一次超载必须与您的 SFINAE 测试相反。我不知道如何进行这样的测试,否则我会发布答案。

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


【解决方案1】:

您可以编写一个特征来检测 typedef 是否存在,例如使用void_t 和基于此的 SFINAE:

template<class...> struct voider { using type = void; };
template<class... Ts> using void_t = typename voider<Ts...>::type;

template<class T, class = void>
struct has_MyTypedef : std::false_type {};

template<class T>
struct has_MyTypedef<T, void_t<typename T::MyTypedef>> : std::true_type {};

template<typename T, std::enable_if_t<has_MyTypedef<T>{}, int> = 0>
void myFunction()
{
    ...
}

template<typename T, std::enable_if_t<!has_MyTypedef<T>{}, int> = 0>
void myFunction()
{
    ...
}

或者编写内部助手,以便您可以使用虚拟参数在两种可行的情况下消除它们之间的歧义:

template<class T, class = typename T::MyTypedef>
void myFunctionImpl(int) {
    // ...
}
template<class T>
void myFunctionImpl(...) {
    // ...
}

template<class T>
void myFunction(){
    return myFunctionImpl<T>(0);
}

【讨论】:

  • 你的第一个代码完全符合我的要求,但是它没有编译。我不得不将 template{}, int> = 0> 更改为 template::value, int>:: type = 0> 成功使用它。
  • 能否请您解释一下 IT 的工作原理?
【解决方案2】:

您的代码无法编译,因为在“具有 typedef”的情况下,两个重载都存在且有效。编译器无法在它们之间进行选择。有 3 种基本方法。

首先,简单地反转条件。您以一种对 sfinae 友好的方式编写“有 typedef”和“没有 typedef”,如果有则启用一个功能,如果没有则启用另一个。

我发现这通常很丑陋并且扩展性很差。

第二种方法是当两者都有效时,通过使用重载解决顺序技巧(继承、可变参数、转换等),首选您想要的那个。这扩展性稍好一些,但我觉得它很老套而且有点脆弱。

第三种方法是标签调度。这不能很好地支持“没有有效的重载”,但在其他方面是干净的。

您编写了一个基本函数。此函数进行一些类型计算并使用结果构建一些标记类型。然后,您使用标签调用您的实现函数,并使用它进行调度。

想象一下你有has_foo&lt;T&gt;的特质。如果T 具有foo 属性,则它是从std::true_type 继承的类型,否则为false_type

那么我们可以:

 void bar_impl(std::true_type);
 void bar_impl(std::false_type);

template<class T>
void bar(T  const&){
  bar_impl( has_foo<T>{} );
}

如果T具有属性foo,则对应的bar_impl会被调用。

编写这样的测试很容易。这是一个小图书馆:

 namespace details{
   template<template<class...>class Z, class, class...>
   struct can_apply:std::false_type{};
   template<template<class...>class Z, class...Ts>
   struct can_apply<Z, decltype(void(std::declval<Z<Ts...>>())), Ts...>:
     std::true_type
   {};
 }
 template<template<class...>class Z,class...Ts>
 using can_apply=typename details::can_apply<Z,void,Ts...>::type;

然后我们只写我们的测试:

template<class T>
using bob_subtype = typename T::bob;

类型是T::bob。我们可以测试某物是否有鲍勃:

template<class T>
using has_bob=can_apply<bob_subtype,T>;

完成了。

【讨论】:

  • 虽然我也喜欢你的解决方案,但我发现它比 T.C. 的解决方案更复杂,而且我真的不想有一个带有虚假参数的内部方法。此外,在我的情况下,具有两个条件的 SFINAE 更简单,因为我知道我处理的案例永远不会超过 2 个。如果不同,您的标签调度方法可能会更好
猜你喜欢
  • 2012-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多