Подтвердить что ты не робот

Почему некоторые стандартные операторы не имеют стандартных функторов?

Имеем:

  • 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[]
  • все операторы указателей функций-членов
  • все составные операторы присваивания.

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

4b9b3361

Ответ 1

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

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

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

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-ссылкам, поэтому, если я сделал с ними что-то не так, надеюсь, кто-то сообщит мне.]

Ответ 2

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

Ответ 3

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

Изменить:

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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