为什么几个标准运算符没有标准函子

Why do several of the standard operators not have standard functors?

本文关键字:标准 运算符 几个 为什么      更新时间:2023-10-16

我们有:

  • 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;
        }
    };
}

我省略了newdelete(以及它们的各种形式),因为用它们编写异常安全代码太难了:-)。

call的实现被限制为零operator()过载;使用可变模板,您可能能够扩展它以支持更广泛的重载,但实际上,您最好使用lambda表达式或库(如std::bind)来处理更高级的调用场景。.*->*的实现也是如此。

提供了剩余的可重载运算符,即使是像sizeofthrow这样愚蠢的运算符。

[上面的代码是独立的;不需要标准库头。我承认我对右值引用还是有点不了解,所以如果我对它们做了什么错误,我希望有人能告诉我。]

原因可能是大多数开发人员都不需要它们。其他人使用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 =,它不在你的列表中]。

位运算符介于两者之间;对他们来说,无论哪种方式,我都能理解这场争论。

列出的所有函数都是两个参数函子。并非以下所有的都是。事实上,只有>><<&|!=满足这一标准,并且就函子而言用处稍小。强制转换尤其是模板,这使它们的用处有所减少。