为什么有几个标准运算符没有标准仿函数?

时间:2011-06-13 20:37:48

标签: c++ stl functor

我们有:

  • 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 style casts
  • new / new[] / delete / delete[]
  • 所有成员函数指针运算符
  • 所有复合赋值运算符。

有没有理由我们没有这些,或者只是一个疏忽?

6 个答案:

答案 0 :(得分:9)

我认为这个问题最可能的答案是包含的运算符是被认为最有用的运算符。如果没有人想要在标准库中添加内容,则不会添加。

我认为运算符函数在C ++ 0x中无用的断言因为lambda表达式更优秀是愚蠢的:当然,lambda表达式很精彩且更灵活,但有时候使用命名的functor会导致更简洁,更清晰,更容易理解代码;另外,命名函子可以是多态的,而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等愚蠢的运算符。

[以上代码是独立的;不需要标准库头。我承认在rvalue引用方面我仍然是一个菜鸟,所以如果我对它们做错了什么,我希望有人会让我知道。]

答案 1 :(得分:6)

原因可能是大多数开发人员不需要它们。其他人使用Boost.Lambda,其中大多数都在那里。

答案 2 :(得分:5)

最有可能的是,标准委员会中没有人认为它们会有用。并且使用C ++ 0x的lambda支持,然后它们都没有用。

编辑:

我并不是说他们没有用 - 委员会中没有人真正想到这种用法。

答案 3 :(得分:2)

在C ++ 0x中添加了按位运算符。我也发现not_equal_to已经存在。

其他人,比如sizeof和一些强制转换是编译时运算符,因此在仿函数中不太有用。

在分配器中抽象新的和删除。我们需要更多吗?

答案 4 :(得分:1)

new每个人都没有仿函数,但默认的分配器只会将请求传递给new。这也涵盖delete,因为这两者是相互关联的。

从那里开始,我认为这些仿函数不应该被认为是“你传递给for_each的东西,而是”你可能需要根据具体情况进行专门研究的事情。“

从列表中删除new [和family],除了语言指定外,你基本上有一堆没有实际意义的操作。如果你拿一个对象的地址,那么你真的只想做一件事:给你那个对象的地址。 如何更改,但 不会更改。因此,从来没有必要通过标准仿函数来专门化这种行为;你可以使用&并信任运算符重载来完成它的工作。但是“添加”或“比较”的含义可能会在程序的过程中发生变化,因此提供这样做的方法有一些优点。

这还包括复合赋值算子;他们的意思与他们的两个部分相关联,所以如果你需要std::add_assign,你可以回到std::add [和operator =,这在你的名单中不存在。“

按位运算符介于两者之间;我可以为他们看到这个论点。

答案 5 :(得分:-1)

列出的所有内容都是双参数函子。并非所有下面的都是。事实上,只有>><<&|!=符合这一标准,并且在仿函数方面稍微有点用处。演员表特别是模板,这使得它们的用处不那么有用。