SFINAE 检查 std::less 是否有效
SFINAE to check if std::less will work
在我的代码中,如果一个对象小于另一个对象,我希望一个操作先于另一个操作发生。但是,如果类型不可比较,则顺序无关紧要。为此,我尝试使用 SFINAE:
template<typename T, typename = decltype(std::declval<std::less<T>>()(std::declval<T>(), std::declval<T>()))>
bool ComparableAndLessThan(const T& lhs, const T& rhs) {
return std::less<T>()(lhs, rhs);
}
bool ComparableAndLessThan(...) {
return false;
}
struct foo {};
int main()
{
foo a,b;
if (ComparableAndLessThan(a, b)) {
std::cout << "a first" << std::endl;
} else {
std::cout << "b first" << std::endl;
}
}
然而这并没有奏效。如果我创建一个对象而不给它一个 operator< 或 std::less 特化,我会得到这个错误。
error C2678: binary '<': no operator found which takes a left-hand operand of type 'const foo' (or there is no acceptable conversion)
note: could be 'bool std::operator <(const std::error_condition &,const std::error_condition &) noexcept'
note: or 'bool std::operator <(const std::error_code &,const std::error_code &) noexcept'
note: while trying to match the argument list '(const foo, const foo)'
note: while compiling class template member function 'bool std::less<T>::operator ()(const _Ty &,const _Ty &) const'
with
[
T=foo,
_Ty=foo
]
我假设因为声明存在,所以 SFINAE 不会将此视为错误,即使实现会导致错误。有什么方法可以检查 std::less 是否可以用于模板类型?
SFINAE 仅适用于模板的直接上下文。在您的情况下,错误发生在 std::less::operator()
但是,您可以先测试 operator < 是否可用;如果没有,请检查 std::less 的专业化是否可用。看看 .
你可以这样re-write。
template<typename T, typename F = typename std::enable_if<std::is_same<decltype(std::declval<T>() < std::declval<T>()), bool>::value, bool>::type>
F ComparableAndLessThan(const T& lhs, const T& rhs) {
return std::less<T>()(lhs, rhs);
}
bool ComparableAndLessThan(...) {
return false;
}
更改检查以查看 decltype(std::declval<T>() < std::declval<T>())
是否产生布尔值。
代码有两个问题。首先很容易修复:默认模板参数不是重载决议的一部分,不应该用于 SFINAE-type 决议。有一个规范的修复,你制作一个 your_decltype
* 类型的 non-type 模板参数并将其默认为 nullptr
.
第二题比较难。即使进行了上述修复,SFINAE 也无法正常工作,因为没有替代错误。 std::less<T>
为每个T
定义,只是在调用operator<
时出现编译错误。解决它的一种方法是直接使用 operator<
作为您的类型:
template<typename T,
decltype(std::declval<T>() < std::declval<T>())* = nullptr>
bool ComparableAndLessThan(const T& lhs, const T& rhs) {
return std::less<T>()(lhs, rhs);
}
bool ComparableAndLessThan(...) {
return false;
}
但这很可能不是你想要的。我不知道如何让它与定义非常广泛的 std::less
.
一起工作
我想用一个简单的例子来说明问题所在。
首先, 是正确的,您需要将其设为 non-type 模板参数默认值 nullptr
。
现在说说为什么它不起作用。
因为 std::less
的 operator()
对 SFINAE 不友好。让我告诉你我的意思:
template <class T> struct Not_working
{
// not SFINAE friendly
auto operator()(T a, T b) { return a < b; }
};
这就是 std::less::operator()
的样子。方法 operator()
是无条件定义的。即使 T
是否有 <
,operator()
存在并且有 有效声明 。错误在函数体中。
当您在 SFINAE 上下文中使用 Not_working::operator()
时,它的主体 不是直接上下文 因此对于 SFINAE 而言,Not_working::operator()
是有效的而不是失败。所以为重载集保留了特化,然后我们有一个错误。
为了在 SFINAE 中可用,如果正文中存在错误,operator()
不得参与重载决议。换句话说,它本身必须是 SFINAEd:
template <class T> struct Working
{
// SFINAE friendly
template <class TT = T, decltype(TT{} < TT{})* = nullptr>
auto operator()(T a, T b) { return a < b; }
};
现在,当 Working::operator()
在 SFINAE 上下文中使用时,例如在您的 ComparableAndLessThan
中,那么我们将遇到不是错误的替换失败,因此 SFINAE 将按预期工作。
以我的愚见,不让这些成员 SFINAE 友好是委员会的疏忽,但可能有一些我没有考虑的因素。
在我的代码中,如果一个对象小于另一个对象,我希望一个操作先于另一个操作发生。但是,如果类型不可比较,则顺序无关紧要。为此,我尝试使用 SFINAE:
template<typename T, typename = decltype(std::declval<std::less<T>>()(std::declval<T>(), std::declval<T>()))>
bool ComparableAndLessThan(const T& lhs, const T& rhs) {
return std::less<T>()(lhs, rhs);
}
bool ComparableAndLessThan(...) {
return false;
}
struct foo {};
int main()
{
foo a,b;
if (ComparableAndLessThan(a, b)) {
std::cout << "a first" << std::endl;
} else {
std::cout << "b first" << std::endl;
}
}
然而这并没有奏效。如果我创建一个对象而不给它一个 operator< 或 std::less 特化,我会得到这个错误。
error C2678: binary '<': no operator found which takes a left-hand operand of type 'const foo' (or there is no acceptable conversion)
note: could be 'bool std::operator <(const std::error_condition &,const std::error_condition &) noexcept'
note: or 'bool std::operator <(const std::error_code &,const std::error_code &) noexcept'
note: while trying to match the argument list '(const foo, const foo)'
note: while compiling class template member function 'bool std::less<T>::operator ()(const _Ty &,const _Ty &) const'
with
[
T=foo,
_Ty=foo
]
我假设因为声明存在,所以 SFINAE 不会将此视为错误,即使实现会导致错误。有什么方法可以检查 std::less 是否可以用于模板类型?
SFINAE 仅适用于模板的直接上下文。在您的情况下,错误发生在 std::less::operator()
但是,您可以先测试 operator < 是否可用;如果没有,请检查 std::less 的专业化是否可用。看看
你可以这样re-write。
template<typename T, typename F = typename std::enable_if<std::is_same<decltype(std::declval<T>() < std::declval<T>()), bool>::value, bool>::type>
F ComparableAndLessThan(const T& lhs, const T& rhs) {
return std::less<T>()(lhs, rhs);
}
bool ComparableAndLessThan(...) {
return false;
}
更改检查以查看 decltype(std::declval<T>() < std::declval<T>())
是否产生布尔值。
代码有两个问题。首先很容易修复:默认模板参数不是重载决议的一部分,不应该用于 SFINAE-type 决议。有一个规范的修复,你制作一个 your_decltype
* 类型的 non-type 模板参数并将其默认为 nullptr
.
第二题比较难。即使进行了上述修复,SFINAE 也无法正常工作,因为没有替代错误。 std::less<T>
为每个T
定义,只是在调用operator<
时出现编译错误。解决它的一种方法是直接使用 operator<
作为您的类型:
template<typename T,
decltype(std::declval<T>() < std::declval<T>())* = nullptr>
bool ComparableAndLessThan(const T& lhs, const T& rhs) {
return std::less<T>()(lhs, rhs);
}
bool ComparableAndLessThan(...) {
return false;
}
但这很可能不是你想要的。我不知道如何让它与定义非常广泛的 std::less
.
我想用一个简单的例子来说明问题所在。
首先,nullptr
。
现在说说为什么它不起作用。
因为 std::less
的 operator()
对 SFINAE 不友好。让我告诉你我的意思:
template <class T> struct Not_working
{
// not SFINAE friendly
auto operator()(T a, T b) { return a < b; }
};
这就是 std::less::operator()
的样子。方法 operator()
是无条件定义的。即使 T
是否有 <
,operator()
存在并且有 有效声明 。错误在函数体中。
当您在 SFINAE 上下文中使用 Not_working::operator()
时,它的主体 不是直接上下文 因此对于 SFINAE 而言,Not_working::operator()
是有效的而不是失败。所以为重载集保留了特化,然后我们有一个错误。
为了在 SFINAE 中可用,如果正文中存在错误,operator()
不得参与重载决议。换句话说,它本身必须是 SFINAEd:
template <class T> struct Working
{
// SFINAE friendly
template <class TT = T, decltype(TT{} < TT{})* = nullptr>
auto operator()(T a, T b) { return a < b; }
};
现在,当 Working::operator()
在 SFINAE 上下文中使用时,例如在您的 ComparableAndLessThan
中,那么我们将遇到不是错误的替换失败,因此 SFINAE 将按预期工作。
以我的愚见,不让这些成员 SFINAE 友好是委员会的疏忽,但可能有一些我没有考虑的因素。