Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?

like image 924
Billy ONeal Avatar asked Jun 13 '11 20:06

Billy ONeal


People also ask

What is the difference between a functor and a function pointer?

A function pointer allows a pointer to a function to be passed as a parameter to another function. Function Objects (Functors) - C++ allows the function call operator() to be overloaded, such that an object instantiated from a class can be "called" like a function.

Are lambdas functors?

Lambdas are basically just syntactic sugar that implement functors (NB: closures are not simple.) In C++0x, you can use the auto keyword to store lambdas locally, and std::function will enable you to store lambdas, or pass them around in a type-safe manner.

What are binary functors?

In mathematics, a binary function (also called bivariate function, or function of two variables) is a function that takes two inputs.


1 Answers

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.]

like image 172
James McNellis Avatar answered Oct 05 '22 23:10

James McNellis