是否有通用的方法来消除SFINAE的decltype条件?

时间:2015-06-10 20:44:20

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

我有十几个函数,它们有两个参数:泛型和特定类型。 E.g:

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())
// ...

我也在更一般的情况下问:给予任何&#34;肯定&#34; SFINAE测试导致功能或类因测试而产生,有没有办法否定该测试专门用于其他情况?实质上,相当于if ... else if与SFINAE。

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

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

3 个答案:

答案 0 :(得分:8)

  

本质上,相当于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>()); }

具有最高等级的可行过载&#34;将通过重载决议选择。

您无法直接否定 ad hoc SFINAE约束(&#34;签名中使用的此表达式必须格式良好&#34;)。您需要编写一个实际特征来检测它,然后否定结果。最简单的方法是使用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;
}

答案 1 :(得分: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的最后一次检查可能是多余的。

答案 2 :(得分:1)

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...,如果true_type是有效表达式,则返回Z<Ts...>

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的功能类似,但是是一个独立的实现。