Почему несколько стандартных операторов не имеют стандартных функторов? - PullRequest
23 голосов
/ 14 июня 2011

У нас есть:

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

У нас нет функторов для:

  • & (адрес-of)
  • * (разыменование)
  • []
  • ,
  • побитовые операторы ~, &, |, ^, <<, >>
  • ++ (префикс / постфикс) / -- (префикс / постфикс)
  • sizeof
  • static_cast / dynamic_cast / reinterpret_cast / const_cast
  • броски в стиле c
  • new / new[] / delete / delete[]
  • все операторы указателей на функции-члены
  • все операторы составного присваивания.

Есть ли причина, по которой у нас их нет, или это просто недосмотр?

Ответы [ 6 ]

9 голосов
/ 23 июня 2011

Я думаю, что наиболее вероятный ответ на этот вопрос заключается в том, что включенные операторы считаются наиболее полезными.Если никто не думает добавить что-то в Стандартную библиотеку, это не будет добавлено.

Я думаю, что утверждение о том, что операторные функторы бесполезны в C ++ 0x, потому что лямбда-выражения превосходят, глупо: конечно,Лямбда-выражения замечательны и гораздо более гибки, но иногда использование именованного функтора может привести к более короткому, понятному и простому для понимания коду;Кроме того, именованные функторы могут быть полиморфными, а лямбды - нет.

Функторы операторов стандартной библиотеки, конечно, не являются полиморфными (они являются шаблонами классов, поэтому типы операндов являются частью типа функтора).Тем не менее, написать свои собственные оператор-функторы не составляет особого труда, и макросы делают задачу довольно простой:

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

Я пропустил new и delete (и их различные формы), потому что это слишкомТрудно написать с ними исключительный код: -).

Реализация call ограничена нулевыми перегрузками operator();с помощью шаблонов с переменным числом аргументов вы могли бы расширить это для поддержки более широкого диапазона перегрузок, но на самом деле вам лучше использовать лямбда-выражения или библиотеку (например, std::bind) для обработки более сложных сценариев вызовов.То же самое касается реализаций .* и ->*.

Предоставляются остальные перегружаемые операторы, даже глупые, такие как sizeof и throw.

[Код вышеавтономный;заголовки стандартной библиотеки не требуются.Я признаю, что я все еще немного новичок в отношении ссылок на rvalue, поэтому, если я сделал с ними что-то не так, я надеюсь, что кто-то сообщит мне.]

6 голосов
/ 14 июня 2011

Возможно, причина в том, что большинству разработчиков они не нужны. Другие используют Boost.Lambda , большинство из них там.

5 голосов
/ 14 июня 2011

Скорее всего, никто в Стандартном комитете не думал, что они будут полезны. А с лямбда-поддержкой C ++ 0x ни один из них не является полезным.

Редактировать:

Я не говорю, что они бесполезны - более того, никто в Комитете на самом деле не думал об этом использовании.

2 голосов
/ 14 июня 2011

Побитовые операторы добавляются в C ++ 0x.Я также нахожу not_equal_to уже присутствует.

Другие, такие как sizeof и некоторые приведенные типы, являются операторами времени компиляции, поэтому они менее полезны в функторе.

New и delete абстрагируются в распределителях.Нам нужно больше этого?

1 голос
/ 14 июня 2011

new не имеет функтора на слово, но распределитель по умолчанию просто передает запрос на new.Это также охватывает delete, так как они связаны между собой.

Оттуда я не думаю, что функторы действительно должны рассматриваться как "вещи, которые вы передаете for_each, а скорее" вещи, которые вы могли бынужно специализироваться в каждом конкретном случае. "

Удаление new [и семейства] из списка, и у вас в основном есть куча операций, которые не имеют реального значения, за исключением случаев, определенных языком. Есливы берете адрес объекта, на самом деле вам нужно только одно: вам дается адрес этого объекта. Как может измениться, но , что не . Так что на самом деле никогда не бываетнужно специализировать это поведение с помощью стандартного функтора, вы можете просто использовать & и перегрузку доверительного оператора, чтобы выполнить свою задачу, но значение «добавить» или «сравнить» может измениться в ходе программы, поэтому предоставление средствэто имеет свои преимущества.

Сюда также входят составные операторы присваивания, их значение связано с их двумя частями, поэтому, если вам нужен std::add_assign, выможет просто вернуться к std::addoperator =, который отсутствует в вашем списке].

Битовые операторы вроде бы оказываются между;Я мог видеть аргумент в любом случае для них.

0 голосов
/ 14 июня 2011

Все перечисленные являются функторами с двумя аргументами.Не все из приведенных ниже.Фактически, только >>, <<, &, | и != удовлетворяют этому критерию и являются чуть менее полезными с точки зрения функторов.В частности, приведения являются шаблонами, что делает их немного менее полезными.

...