有没有一种通用的方法可以用 SFINAE 否定 decltype 条件?
Is there a generic way to negate a decltype condition with SFINAE?
我有十几个函数接受两个参数:一个泛型和一个特定类型。例如:
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())
// ...
我也在更一般的情况下问:给定任何 "positive" SFINAE 测试导致函数或 class 作为测试的结果被创建,是否有任何方法可以否定它测试专门用于其他情况?本质上,相当于 if ... else if
与 SFINAE.
我确实让这个案例起作用了,但我不得不将所有 foo
重命名为 foo_imp
,为通用参数添加一个 long
参数,一个 int
专门的参数,然后定义一个 foo
来调用它们(ideone 代码 here)。这似乎不太理想,因为它不是那么简单,尽管无论如何我都必须修改所有现有的 foo
s。
[1] 请注意,我不能使用类型的名称,因为它是一个嵌套模板,因此会导致不可推导的上下文。
In essence, the equivalent of if ... else if
with 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>()); }
重载决议将选择具有最高 "rank" 的可行重载。
您不能直接否定 ad hoc SFINAE 约束 ("this expression used in the signature must be well-formed")。您需要编写一个实际特征来检测它,然后否定结果。最简单的方法可能是使用 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;
}
首先,检查给定类型是否为"special"的模板:
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 表达式,这些表达式根据传递的类型 enable/disable 相关函数。以下代码特别为您的特殊类型禁用任何通用回调。
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;
}
请注意,专门回退中对 is_special
的最后检查可能是多余的。
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...
,并且 returns true_type
当且仅当 Z<Ts...>
是一个有效的表达式。
template<class T>
using is_special_t = decltype( T::is_special );
采用 T
,returns 其 is_special
类型。
template<class T>
using has_special = can_apply< is_special_t, T >;
解决了您的问题。
这在功能上类似于std::experimental::is_detected
,但是是一个独立的实现。
我有十几个函数接受两个参数:一个泛型和一个特定类型。例如:
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())
// ...
我也在更一般的情况下问:给定任何 "positive" SFINAE 测试导致函数或 class 作为测试的结果被创建,是否有任何方法可以否定它测试专门用于其他情况?本质上,相当于 if ... else if
与 SFINAE.
我确实让这个案例起作用了,但我不得不将所有 foo
重命名为 foo_imp
,为通用参数添加一个 long
参数,一个 int
专门的参数,然后定义一个 foo
来调用它们(ideone 代码 here)。这似乎不太理想,因为它不是那么简单,尽管无论如何我都必须修改所有现有的 foo
s。
[1] 请注意,我不能使用类型的名称,因为它是一个嵌套模板,因此会导致不可推导的上下文。
In essence, the equivalent of
if ... else if
with 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>()); }
重载决议将选择具有最高 "rank" 的可行重载。
您不能直接否定 ad hoc SFINAE 约束 ("this expression used in the signature must be well-formed")。您需要编写一个实际特征来检测它,然后否定结果。最简单的方法可能是使用 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;
}
首先,检查给定类型是否为"special"的模板:
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 表达式,这些表达式根据传递的类型 enable/disable 相关函数。以下代码特别为您的特殊类型禁用任何通用回调。
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;
}
请注意,专门回退中对 is_special
的最后检查可能是多余的。
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...
,并且 returns true_type
当且仅当 Z<Ts...>
是一个有效的表达式。
template<class T>
using is_special_t = decltype( T::is_special );
采用 T
,returns 其 is_special
类型。
template<class T>
using has_special = can_apply< is_special_t, T >;
解决了您的问题。
这在功能上类似于std::experimental::is_detected
,但是是一个独立的实现。