为什么几个标准运算符没有标准函子
Why do several of the standard operators not have standard functors?
我们有:
std::plus
(+
)std::minus
(-
)std::multiplies
(*
)std::divides
(/
)std::modulus
(%
)std::negate
(-
)std::logical_or
(||
)std::logical_not
(!
)std::logical_and
(&&
)std::equal_to
(==
)std::not_equal_to
(!=
)std::less
(<
)std::greater
(>
)std::less_equal
(<=
)std::greater_equal
(>=
)
我们没有的函子
&
(地址)*
(取消引用)[]
,
- 按位运算符
~
、&
、|
、^
、<<
、>>
++
(前缀/后缀)/--
(前缀/前缀)sizeof
static_cast
/dynamic_cast
/reinterpret_cast
/const_cast
- c样式强制转换
new
/new[]
/delete
/delete[]
- 所有成员函数指针运算符
- 所有的复合赋值运算符
我们没有这些是有原因的,还是只是疏忽?
我认为这个问题最有可能的答案是,所包含的运算符被认为是最有用的。如果没有人想向标准库添加某些内容,则不会添加。
我认为断言运算符函子在C++0x中是无用的,因为lambda表达式是高级的,这是愚蠢的:当然,lambda表达式非常好,而且更灵活,但有时使用命名函子可以产生更简洁、更干净、更容易理解的代码;此外,命名函子可以是多态的,而lambdas不能。
标准库运算符函子当然不是多态的(它们是类模板,因此操作数类型是函子类型的一部分)。不过,编写自己的运算符函子并不特别困难,宏使任务变得非常简单:
namespace ops
{
namespace detail
{
template <typename T>
T&& declval();
template <typename T>
struct remove_reference { typedef T type; }
template <typename T>
struct remove_reference<T&> { typedef T type; }
template <typename T>
struct remove_reference<T&&> { typedef T type; }
template <typename T>
T&& forward(typename remove_reference<T>::type&& a)
{
return static_cast<T&&>(a);
}
template <typename T>
T&& forward(typename remove_reference<T>::type& a)
{
return static_cast<T&&>(a);
}
template <typename T>
struct subscript_impl
{
subscript_impl(T&& arg) : arg_(arg) {}
template <typename U>
auto operator()(U&& u) const ->
decltype(detail::declval<U>()[detail::declval<T>()])
{
return u[arg_];
}
private:
mutable T arg_;
};
}
#define OPS_DEFINE_BINARY_OP(name, op)
struct name
{
template <typename T, typename U>
auto operator()(T&& t, U&& u) const ->
decltype(detail::declval<T>() op detail::declval<U>())
{
return detail::forward<T>(t) op detail::forward<U>(u);
}
}
OPS_DEFINE_BINARY_OP(plus, + );
OPS_DEFINE_BINARY_OP(minus, - );
OPS_DEFINE_BINARY_OP(multiplies, * );
OPS_DEFINE_BINARY_OP(divides, / );
OPS_DEFINE_BINARY_OP(modulus, % );
OPS_DEFINE_BINARY_OP(logical_or, || );
OPS_DEFINE_BINARY_OP(logical_and, && );
OPS_DEFINE_BINARY_OP(equal_to, == );
OPS_DEFINE_BINARY_OP(not_equal_to, != );
OPS_DEFINE_BINARY_OP(less, < );
OPS_DEFINE_BINARY_OP(greater, > );
OPS_DEFINE_BINARY_OP(less_equal, <= );
OPS_DEFINE_BINARY_OP(greater_equal, >= );
OPS_DEFINE_BINARY_OP(bitwise_and, & );
OPS_DEFINE_BINARY_OP(bitwise_or, | );
OPS_DEFINE_BINARY_OP(bitwise_xor, ^ );
OPS_DEFINE_BINARY_OP(left_shift, << );
OPS_DEFINE_BINARY_OP(right_shift, >> );
OPS_DEFINE_BINARY_OP(assign, = );
OPS_DEFINE_BINARY_OP(plus_assign, += );
OPS_DEFINE_BINARY_OP(minus_assign, -= );
OPS_DEFINE_BINARY_OP(multiplies_assign, *= );
OPS_DEFINE_BINARY_OP(divides_assign, /= );
OPS_DEFINE_BINARY_OP(modulus_assign, %= );
OPS_DEFINE_BINARY_OP(bitwise_and_assign, &= );
OPS_DEFINE_BINARY_OP(bitwise_or_assign, |= );
OPS_DEFINE_BINARY_OP(bitwise_xor_assign, ^= );
OPS_DEFINE_BINARY_OP(left_shift_assign, <<=);
OPS_DEFINE_BINARY_OP(right_shift_assign, >>=);
#define OPS_DEFINE_COMMA() ,
OPS_DEFINE_BINARY_OP(comma, OPS_DEFINE_COMMA());
#undef OPS_DEFINE_COMMA
#undef OPS_DEFINE_BINARY_OP
#define OPS_DEFINE_UNARY_OP(name, pre_op, post_op)
struct name
{
template <typename T>
auto operator()(T&& t) const ->
decltype(pre_op detail::declval<T>() post_op)
{
return pre_op detail::forward<T>(t) post_op;
}
}
OPS_DEFINE_UNARY_OP(dereference, * , );
OPS_DEFINE_UNARY_OP(address_of, & , );
OPS_DEFINE_UNARY_OP(unary_plus, + , );
OPS_DEFINE_UNARY_OP(logical_not, ! , );
OPS_DEFINE_UNARY_OP(negate, - , );
OPS_DEFINE_UNARY_OP(bitwise_not, ~ , );
OPS_DEFINE_UNARY_OP(prefix_increment, ++, );
OPS_DEFINE_UNARY_OP(postfix_increment, , ++);
OPS_DEFINE_UNARY_OP(prefix_decrement, --, );
OPS_DEFINE_UNARY_OP(postfix_decrement, , --);
OPS_DEFINE_UNARY_OP(call, , ());
OPS_DEFINE_UNARY_OP(throw_expr, throw , );
OPS_DEFINE_UNARY_OP(sizeof_expr, sizeof , );
#undef OPS_DEFINE_UNARY_OP
template <typename T>
detail::subscript_impl<T> subscript(T&& arg)
{
return detail::subscript_impl<T>(detail::forward<T>(arg));
}
#define OPS_DEFINE_CAST_OP(name, op)
template <typename Target>
struct name
{
template <typename Source>
Target operator()(Source&& source) const
{
return op<Target>(source);
}
}
OPS_DEFINE_CAST_OP(const_cast_to, const_cast );
OPS_DEFINE_CAST_OP(dynamic_cast_to, dynamic_cast );
OPS_DEFINE_CAST_OP(reinterpret_cast_to, reinterpret_cast);
OPS_DEFINE_CAST_OP(static_cast_to, static_cast );
#undef OPS_DEFINE_CAST_OP
template <typename C, typename M, M C::*PointerToMember>
struct get_data_member
{
template <typename T>
auto operator()(T&& arg) const ->
decltype(detail::declval<T>().*PointerToMember)
{
return arg.*PointerToMember;
}
};
template <typename C, typename M, M C::*PointerToMember>
struct get_data_member_via_pointer
{
template <typename T>
auto operator()(T&& arg) const ->
decltype(detail::declval<T>()->*PointerToMember)
{
return arg->*PointerToMember;
}
};
}
我省略了new
和delete
(以及它们的各种形式),因为用它们编写异常安全代码太难了:-)。
call
的实现被限制为零operator()
过载;使用可变模板,您可能能够扩展它以支持更广泛的重载,但实际上,您最好使用lambda表达式或库(如std::bind
)来处理更高级的调用场景。.*
和->*
的实现也是如此。
提供了剩余的可重载运算符,即使是像sizeof
和throw
这样愚蠢的运算符。
[上面的代码是独立的;不需要标准库头。我承认我对右值引用还是有点不了解,所以如果我对它们做了什么错误,我希望有人能告诉我。]
原因可能是大多数开发人员都不需要它们。其他人使用Boost。Lambda,大多数都在那里。
很可能,标准委员会中没有人认为它们有用。有了C++0x的lambda支持,它们都没有用。
编辑:
我并不是说它们没有用——更重要的是,委员会中没有人真正想过这种用途。
在C++0x中添加了按位运算符。我还发现not_equal_to已经存在。
其他的,比如sizeof和一些类型转换是编译时运算符,所以在函子中用处不大。
New和delete在分配器中被抽象。我们还需要更多吗?
new
没有函数,但默认分配器只是将请求传递给new
。这也包括delete
,因为两者是链接的。
从那以后,我不认为函子真的应该被认为是"传递给for_each
的东西,而是"你可能需要根据具体情况进行专门化的东西。"
从列表中删除new
[和族],基本上就有了一堆没有实际意义的操作,除非由语言指定。如果你取一个对象的地址,实际上只有一件事你想发生:你得到了该对象的地址如何可能会改变,但什么不会。因此,从来没有真正需要通过标准函子来专门化这种行为;您可以使用&
和信任运算符重载来完成它的工作。但"添加"或"比较"的含义可能会随着程序的进程而改变,因此提供这样做的方法有一些好处。
这也包括复合赋值运算符;它们的意义与它们的两个部分有关,所以如果你需要std::add_assign
,你可以求助于std::add
[和operator =
,它不在你的列表中]。
位运算符介于两者之间;对他们来说,无论哪种方式,我都能理解这场争论。
列出的所有函数都是两个参数函子。并非以下所有的都是。事实上,只有>>
、<<
、&
、|
和!=
满足这一标准,并且就函子而言用处稍小。强制转换尤其是模板,这使它们的用处有所减少。
- 标准库类型的赋值运算符的引用限定符
- 标准::变体的赋值运算符
- 标准::可选枚举的比较运算符
- 重载运算符 + 用于向量:命名空间标准
- 与标准中的"运算符<<"不匹配
- 如何为缺少预定义运算符而不扩展命名空间"std"的标准类型定义运算符>> (istream &, ...)?
- c++标准是否指定了运算符&&(内置)的求值顺序?
- 运算符的要求<恒定性在标准::stable_sort
- 标准::矢量的通用运算符>>
- C++ - <<运算符重载,链表 - 地址而不是标准输出
- 为什么标准在移动分配运算符中使用交换?
- 在全局命名空间中重载不依赖于用户定义类型的标准定义类型的运算符是否格式正确?
- 标准::设置运算符的使用<
- 视觉C 在操作数中向标准运算符中的隐式转换
- 标准运算符的函数指针
- 在哪里定义了标准运算符函数
- 如何在不使用任何标准运算符(如 *,-,/,% 等)的情况下将数字与 3.5 相乘
- 重载标准运算符<<对于 std::complex
- 是否可以获取内置标准运算符的函数指针
- 为什么几个标准运算符没有标准函子