24

We have:

  • 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 (>=)

We don't have functors for:

  • & (address-of)
  • * (dereference)
  • []
  • ,
  • bitwise operators ~, &, |, ^, <<, >>
  • ++ (prefix/postfix) / -- (prefix/postfix)
  • sizeof
  • static_cast / dynamic_cast / reinterpret_cast / const_cast
  • c style casts
  • new / new[] / delete / delete[]
  • all of the member function pointer operators
  • all of the compound assignment operators.

Is there a reason we don't have those, or is it just an oversight?

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • 1
    I've reformatted your question to put the operators in a list, if you don't mind. – In silico Jun 13 '11 at 20:50
  • @In silico: Don't mind at all. – Billy ONeal Jun 13 '11 at 20:50
  • 1
    Actually more than wondering why those are missing I'm surprised about the ones that made it to the standard library. Apparently once again the lesson is to never understimate the power of the committee effect. – 6502 Jun 13 '11 at 21:03
  • 1
    @6502: I'd at least have expected the bitwise operators and dereferencing. (Also, why not `!=` given that they have the completely redundant `std::greater` (e.g. `std::not1(std::less_equal)` would suffice just fine)) – Billy ONeal Jun 13 '11 at 21:07
  • 2
    The most useful operator in C++: the unary plus. ;-) – James McNellis Jun 14 '11 at 06:02
  • @James: Lol -- I thought that just got folded in as part of the literal. Learn something every day I guess. Don't think that operator should even exist :P – Billy ONeal Jun 14 '11 at 06:03

6 Answers6

9

I think the most likely answer to the question is that the included operators are the ones that were thought to be most useful. If no one thinks to add something to the Standard Library, it won't get added.

I think the assertion that the operator functors are useless in C++0x because lambda expressions are superior is silly: sure, lambda expressions are wonderful and far more flexible, but sometimes using a named functor can lead to terser, cleaner, easier to understand code; in addition, named functors can be polymorphic while lambdas cannot.

The Standard Library operator functors are, of course, not polymorphic (they are class templates, so the operand types are part of the type of the functor). It's not particularly difficult to write your own operator functors, though, and macros make the task quite straightforward:

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

I have omitted new and delete (and their various forms) because it is too difficult to write exception-safe code with them :-).

The call implementation is limited to nullary operator() overloads; with variadic templates you might be able to extend this to support a wider range of overloads, but really you'd be better off using lambda expressions or a library (like std::bind) to handle more advanced call scenarios. The same goes for the .* and ->* implementations.

The remaining overloadable operators are provided, even the silly ones like sizeof and throw.

[The above code is standalone; no Standard Library headers are required. I admit I am a bit of a noob still with respect to rvalue references, so if I've done something wrong with them I hope someone will let me know.]

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • I'm not sure, but I think you might need to `forward` in the trailing return type too. Again, not sure. – Xeo Jun 23 '11 at 05:26
  • @Xeo: The `decltype` should already give `T&&` or `T&` depending on whether the expression is an xvalue or an lvalue. @Billy: `);` is a frowny face, not an lol face. ;-) – James McNellis Jun 23 '11 at 05:37
6

The reason probably is that most of developers doesn't need them. Others use Boost.Lambda, most of them are there.

Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
  • And now with C++0x language lambda support, there's even less reason to add them to the standard library. – Ben Voigt Jun 13 '11 at 20:45
  • 1
    @Ben: Except the standard ones are polymorphic, while all lambdas are monomorphic. That is, if I'm writing something like [this](http://stackoverflow.com/q/6335105/82320) I can't use lambdas, because I can't specify things inline. – Billy ONeal Jun 13 '11 at 20:47
  • @Billy `std::plus` accepts `T const&` so it's monomorphic (I do get your point about `std::plus` the template though). The Boost.Lambda (and Phoenix, too) stuff is polymorphic without any work at all (`_1 + _2` or `arg1 + arg2`). – Luc Danton Jun 13 '11 at 21:50
  • @Luc: How is that monomorphic? It's compile time polymorphism. – Billy ONeal Jun 13 '11 at 21:51
  • @Billy `std::plus` will not accept `(U, U)` (assuming no implicit conversions). The Boost stuff will as long as `u + u` works. So there's no `std::plus` you can use with e.g. the elements of a `std::tuple` (warning: example is seriously contrived and not meant to be an actual problem to solve). – Luc Danton Jun 13 '11 at 21:54
  • @Luc: That doesn't mean that it's not polymorphic, it merely means that it doesn't work for that specific scenario. – Billy ONeal Jun 13 '11 at 22:02
  • 1
    @Billy: You're arguing semantics. This is like me saying "C++ may inline functions at compile-time" and you say "well, no, that's run-time...the compiler is the one running." – GManNickG Jun 13 '11 at 22:08
  • @Billy Then lambdas are just as polymorphic :). I think you mean that the *template* is polymorphic. Notice that I acknowledged that in my posts, and I always mention that `std::plus` (which is a type, not a template) is monomorphic. `arg1 + arg2` however never mentions any template parameter! – Luc Danton Jun 13 '11 at 22:10
5

Most likely, nobody on the Standard committee thought that they would be useful. And with C++0x's lambda support, then none of them are useful.

Edit:

I'm not saying that they have no use- more that nobody on the Committee actually thought of that use.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 2
    They're still useful because they can be overridden for specific types, even if you can't change the implementation of the types. – Mark Ransom Jun 13 '11 at 20:56
  • @Mark: DeadMG isn't saying it isn't useful to pass in a functor to customize behavior. It's just no longer much benefit to have functors written by the library, when the code to recreate them has become so short. – Ben Voigt Jun 13 '11 at 21:10
  • @Mark Ransom: I'm also not saying that they necessarily have no use- just that nobody on the Committee actually *thought of* that use. – Puppy Jun 13 '11 at 21:33
  • @Ben Voigt, take for example std::sort which uses std::less as a default template parameter. You can provide a default ordering for a class by creating an override for `std::less(const myclass&,const myclass&)` - this wouldn't be possible if std::less used a lambda. – Mark Ransom Jun 13 '11 at 21:49
  • 1
    @Mark: That's rather ugly, though; as controversial as it may be, I'd ban that. If my class didn't implement `operator<`, who are you to say `std::less` should be well-defined for it? If I wanted it to be less-than comparable, I would have made it so. – GManNickG Jun 13 '11 at 22:12
  • @GMan: Perhaps it's comparable only in a specific subset of cases- for example, you may not choose to overload `operator<` for a base class but find that a derived class with it makes sense. – Puppy Jun 14 '11 at 00:26
  • 1
    @DeadMG: Then define `operator<` in the derived class. @Mark: Since there is a template parameter, it's not necessary to have `std::less` in order to use a custom sort order. @GMan: You're entitled to specialize `std::less` for any class you write (with a broken implementation) and thus block its use. By I agree you shouldn't have to. – Ben Voigt Jun 14 '11 at 00:56
2

The bitwise operators are added in C++0x. I also find not_equal_to already present.

Others, like sizeof and some casts are compile time operators so would be less useful in a functor.

New and delete are abstracted in allocators. Do we need more of that?

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • I was thinking more about listing all of them, less about which particular ones make sense. In the case of `sizeof` I agree with you, because it's compile time, but I could see the cast being useful. (For example, being able to call `std::for_each` on a member function by downcasting the pointer first with `dynamic_cast`) – Billy ONeal Jun 13 '11 at 21:28
  • (You're right about `not_equal_to` -- fixed it in the question) +1 for that – Billy ONeal Jun 13 '11 at 21:29
1

new does not have a functor per say, but the default allocator does simply pass along the request to new. This also covers delete, since the two are linked.

From there, I don't think the functors are really meant to be considered "things you pass to for_each, but rather "things you might need to specialize on a case by case basis."

Removing new [and family] from the list, and you basically have a bunch of operations that have no real meaning except as specified by the language. If you take an object's address, there's really only one thing you want to happen: you get given that object's address. How might change, but what doesn't. So there's never really a need to specialize that behavior via a standard functor; you can just use & and trust operator overloading to do its thing. But the meaning of "add" or "compare" might change over the course of a program, so providing a means to do so has some merit.

This also includes the compound assignment operators; their meaning is linked to their two pieces, so if you need std::add_assign you can just fall back on std::add [and operator =, which is absent from your list].

The bitwise operators kind of fall in between; I could see the argument either way for them.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38
-1

All of the ones listed are two-argument functors. Not all of the ones below are. In fact, only >>, <<, &, |, and != fulfill that criterion, and are just a little less useful in terms of functors. The casts especially are templates, which makes them a little less useful.

RC Howe
  • 509
  • 3
  • 16