【问题标题】:Is there a generic way to negate a decltype condition with SFINAE?有没有一种通用的方法来否定 SFINAE 的 decltype 条件?
【发布时间】:2015-06-10 20:44:20
【问题描述】:

我有十几个函数,它们接受两个参数:一个泛型和一个特定类型。例如:

template <class A, class B>
void foo(A& a, B& b)
{
    cout << "generic fallback" << endl;
}

template <class A>
void foo(A& a, int &i)
{
    cout << "generic int" << endl;
}

template <class A>
void foo(A& a, string& s)
{
    cout << "generic str" << endl;
}

我想创建一个重载,只要A 是特定结构[1] 的实例,它就会被调用。到目前为止我想出的最好的是:

struct mine
{
    int is_special;
};

template <class A, class B>
auto foo(A& a, B& b) -> decltype(A::is_special, void())
{
    cout << "specialized fallback" << endl;
}

我想要的结果是:

int x;
string y;
float z;
string generic;
mine special;

foo(generic, x); // generic int
foo(generic, y); // generic string
foo(generic, z); // generic fallback
foo(special, x); // specialized fallback
foo(special, y); // specialized fallback
foo(special, z); // specialized fallback

但是,上面的代码不起作用,因为对于特殊情况,存在模棱两可的重载。是否有任何简单的方法可以仅在 A::is_special 不是有效类型时才创建这些函数?理想情况下,我会用以下内容注释每个函数:

template <class A, class B>
auto foo(A& a, B& b) -> decltype(doesnt_work(A::is_special), void())
// ...

我还在更一般的情况下问:鉴于任何“肯定”的 SFINAE 测试会导致作为测试结果创建函数或类,有没有办法专门否定该测试以用于其他情况?本质上,相当于if ... else if 和 SFINAE。

我确实让这个案例工作了,但我必须将所有 foo 重命名为 foo_imp,将 long 参数添加到通用参数,将 int 参数添加到专用参数,然后定义一个foo 调用它们(ideone 代码 here)。这似乎不太理想,因为它并不那么简单,尽管无论如何我都必须修改所有现有的foos。


[1] 请注意,我不能使用该类型的名称,因为它是一个嵌套模板,因此会导致不可推断的上下文。

【问题讨论】:

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


    【解决方案1】:

    本质上,相当于if ... else if 和 SFINAE。

    您可以使用额外的参数手动控制重载分辨率:

    template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); };
    template<> struct rank<0> {};
    
    template <class A, class B>
    auto foo(A& a, B& b, rank<10>) -> /*some SFINAE */
    {
    }
    
    template <class A, class B>
    auto foo(A& a, B& b, rank<9>) -> /* some other SFINAE */
    {
    }
    
    // ...
    
    template <class A, class B>
    void foo(A& a, B& b, rank<0>)
    {
    // fallback
    }
    
    template <class A, class B>
    void foo(A& a, B& b) { return foo(a, b, rank<20>()); }
    

    重载决议将选择具有最高“等级”的可行重载。

    您不能直接否定 ad hoc SFINAE 约束(“签名中使用的这个表达式必须格式正确”)。您需要编写一个实际的特征来检测它,然后否定结果。最简单的方法可能是使用 std::experimental::is_detected_v,最近投票加入了库基础 TS 的 v2:

    template<class T>
    using is_special_t = decltype(T::is_special);
    
    template <class A, class B>
    auto foo(A& a, B& b) -> std::enable_if_t<!std::experimental::is_detected_v<is_special_t, A>>
    {
        cout << "generic fallback" << endl;
    }
    
    template <class A, class B>   
    auto foo(A& a, B& b) -> std::enable_if_t<std::experimental::is_detected_v<is_special_t, A>>
    {
        cout << "specialized fallback" << endl;
    }
    

    【讨论】:

      【解决方案2】:

      首先,检查给定类型是否“特殊”的模板:

      template<typename ...> using void_t = void;
      
      template< typename, typename = void >
      struct is_special : public std::false_type {};
      
      template< typename T >
      struct is_special<T, void_t<decltype(T::is_special)> > : public std::true_type {};
      

      除了void_t,您还可以使用decltype(T::is_special, void() ) 构造。

      接下来,您可以插入一些 SFINAE 表达式,根据传递的类型启用/禁用相关功能。以下代码特别禁用了您的特殊类型的任何通用回调。

      template <class A, class B, typename = std::enable_if_t<!is_special<A>::value> >
      void foo(A& a, B& b)
      {
          cout << "generic fallback" << endl;
      }
      
      template <class A, typename = std::enable_if_t<!is_special<A>::value>>
      void foo(A& a, int &i)
      {
          cout << "generic int" << endl;
      }
      
      template <class A, typename = std::enable_if_t<!is_special<A>::value>>
      void foo(A& a, string& s)
      {
          cout << "generic str" << endl;
      }
      
      //this SFINAE check is probably redundant    
      template <class A, class B, typename = std::enable_if_t<is_special<A>::value> >   
      auto foo(A& a, B& b)
      {
          cout << "specialized fallback" << endl;
      }
      

      DEMO

      请注意,在专用回退中最后一次检查 is_special 可能是多余的。

      【讨论】:

      • 很好..这似乎最接近我的要求:一种指定任意特征(使用decltype)然后在任何地方否定它的方法。
      【解决方案3】:
      template<class...>struct voider{using type=void;};
      template<class...Ts>using void_t=typename voider<Ts...>::type;
      

      std::void_t 不在 C++11 中。以上将适用于我尝试过的每个 C++11 编译器。 (由于 C++11 语言缺陷,一步别名会破坏一些编译器)

      namespace details {
        template<template<class...>class Z, class, class...Ts>
        struct can_apply : std::false_type {};
        template<template<class...>class Z, class...Ts>
        struct can_apply<Z, /*std::*/ void_t<Z<Ts...>>, Ts...>: std::true_type {};
      }
      template<template<class...>class Z, class...Ts>
      using can_apply = details::can_apply<Z, void, Ts...>;
      

      这需要一个模板Z 和一组参数Ts...,如果Z&lt;Ts...&gt; 是一个有效的表达式,则返回true_type

      template<class T>
      using is_special_t = decltype( T::is_special );
      

      接受T,并返回其is_special 类型。

      template<class T>
      using has_special = can_apply< is_special_t, T >;
      

      解决您的问题。

      这和std::experimental::is_detected功能类似,但是是一个独立的实现。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多