【问题标题】:Why do several of the standard operators not have standard functors?为什么一些标准运算符没有标准函子?
【发布时间】:2011-09-14 05:28:55
【问题描述】:

我们有:

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

我们没有这些是有原因的,还是只是疏忽?

【问题讨论】:

  • 如果您不介意的话,我已经重新格式化了您的问题,将运算符列在一个列表中。
  • @In silico:别介意。
  • 实际上不仅仅是想知道为什么缺少这些,我对那些进入标准库的那些感到惊讶。显然再一次的教训是永远不要低估委员会效应的力量。
  • @6502:我至少预料到了按位运算符和取消引用。 (另外,为什么不!=,因为他们有完全多余的std::greater(例如std::not1(std::less_equal)就足够了))
  • C++ 中最有用的运算符:一元加号。 ;-)

标签: c++ stl functor


【解决方案1】:

我认为这个问题最有可能的答案是包含的运算符是被认为最有用的运算符。如果没有人想向标准库添加一些东西,它就不会被添加。

我认为断言操作符仿函数在 C++0x 中是无用的,因为 lambda 表达式更优越是愚蠢的:当然,lambda 表达式很棒而且更灵活,但有时使用命名仿函数会导致更简洁、更简洁、更容易理解代码;此外,命名函子可以是多态的,而 lambda 则不能。

当然,标准库运算符函子不是多态的(它们是类模板,因此操作数类型是函子类型的一部分)。不过,编写自己的运算符函子并不是特别困难,而且宏使任务变得非常简单:

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)来处理更高级的调用场景。 .*-&gt;* 实现也是如此。

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

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

【讨论】:

  • 我不确定,但我认为您可能也需要在尾随返回类型中使用forward。同样,不确定。
  • @Xeo:decltype 应该已经给出 T&amp;&amp;T&amp;,具体取决于表达式是 xvalue 还是 lvalue。 @Billy:); 是皱眉脸,不是笑脸。 ;-)
【解决方案2】:

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

【讨论】:

  • 现在有了 C++0x 语言 lambda 支持,将它们添加到标准库的理由就更少了。
  • @Ben:除了标准的是多态的,而所有的 lambda 都是单态的。也就是说,如果我在写 this 这样的东西,我就不能使用 lambda,因为我不能内联指定东西。
  • @Billy std::plus&lt;T&gt; 接受 T const&amp; 所以它是单态的(我明白你关于 std::plus 模板的观点)。 Boost.Lambda(以及 Phoenix)的东西是多态的,根本不需要任何工作(_1 + _2arg1 + arg2)。
  • @Luc:单态性如何?这是编译时多态性。
  • @Billy:你在争论语义。这就像我说“C++ 可能在编译时内联函数”而你说“嗯,不,那是运行时......编译器是运行的。”
【解决方案3】:

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

编辑:

我并不是说它们没有用——更多的是委员会中没有人真正想到过这种用途。

【讨论】:

  • 它们仍然很有用,因为它们可以被特定类型覆盖,即使您无法更改类型的实现。
  • @Mark:DeadMG 并不是说​​传入函子来自定义行为没有用。当重新创建函子的代码变得如此之短时,由库编写函子不再有什么好处。
  • @Mark Ransom:我也不是说它们一定没有用——只是委员会中没有人真正想到这种用途。
  • @Mark:不过,这很丑;尽管可能有争议,但我会禁止它。如果我的班级没有实现operator&lt;,你说std::less 应该为它明确定义吗?如果我希望它具有可比性,我会做到的。
  • @DeadMG:然后在派生类中定义operator&lt;。 @Mark:因为有一个模板参数,所以不需要std::less 来使用自定义排序顺序。 @GMan:您有权为您编写的任何类(具有损坏的实现)专门化std::less,从而阻止它的使用。我同意你不应该这样做。
【解决方案4】:

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

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

new 和 delete 在分配器中抽象出来。我们还需要更多吗?

【讨论】:

  • 我更多地考虑列出所有这些,而不是哪些特定的有意义。在sizeof 的情况下,我同意你的看法,因为它是编译时间,但我可以看到演员表很有用。 (例如,可以通过首先使用dynamic_cast 向下转换指针来调用成员函数上的std::for_each
  • (你是对的 not_equal_to - 已在问题中修正)为此 +1
【解决方案5】:

new 没有一个函子,但默认分配器只是将请求传递给new。这也包括delete,因为两者是相关联的。

从那里开始,我不认为函子真的应该被认为是“你传递给 for_each 的东西,而是“你可能需要根据具体情况专门化的东西。”

从列表中删除new [and family],你基本上有一堆没有实际意义的操作,除非由语言指定。如果您获取一个对象的地址,您实际上只希望发生一件事:您将获得该对象的地址。 如何 可能会改变,但 what 不会。所以从来没有真正需要通过标准函子专门化这种行为。您可以只使用&amp; 并信任运算符重载来完成它的工作。但是“添加”或“比较”的含义可能会随着程序的进行而改变,因此提供一种这样做的方法是有好处的。

这也包括复合赋值运算符;它们的含义与它们的两个部分有关,因此如果您需要std::add_assign,您可以返回std::add [和operator =,您的列表中没有]。

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

【讨论】:

    【解决方案6】:

    所有列出的都是两个参数的函子。并非所有以下都是。事实上,只有&gt;&gt;&lt;&lt;&amp;|!= 满足该标准,并且在函子方面的用处稍差。演员表尤其是模板,这使得它们不太有用。

    【讨论】:

    • 演员表不是模板。它们可能与模板共享语法,但实际上它们是运算符。你不这样做std::dynamic_cast
    • 另外,std::negatestd::logical_not 是一元函子。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    • 2014-06-28
    • 2016-01-07
    相关资源
    最近更新 更多