Синтаксический сахар: автоматическое создание простых функциональных объектов - PullRequest
4 голосов
/ 09 декабря 2011

Я должен реализовать набор шаблонов классов и две специальные переменные, _1 и _2.

Они должны сделать следующий юридический код:

// Sort ascending
std::sort(a, a+5, _1 > _2);

// Output to a stream
std::for_each(a, a+5, std::cout << _1 << " ");

// Assign 100 to each element
std::for_each(a, a+5, _1 = 100);

// Print elements increased by five 5
std::transform(a, a+5, std::ostream_iterator<int>(std::cout, " "), _1 + 5);

Полагаю, что _1 * 5 также должна давать унарную функцию, а также _1 / 5 и т. Д.

  • Повышение не допускается
  • Лямбды не допускаются

Теперь у меня очень небольшой опыт работы с шаблонами и метапрограммированием шаблонов, поэтому я даже не знаю, с чего начать и как должна выглядеть структура моих шаблонов классов. Я особенно запутался, так как не знаю, если внутри моих шаблонов классов мне придется писать реализации для всех этих operator=, operator>>, operator+, ...-, ...*, .../ отдельно или есть более общий способ сделать это.

Буду особенно признателен за ответ с примером реализации этих операторов; шаблоны все еще кажутся мне большим беспорядком.

Ответы [ 2 ]

5 голосов
/ 09 декабря 2011

Хорошо!Это действительно сложная домашняя задача!Но это также очень хорошая проблема для работы и обучения.

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

Например, предположим, что у вас есть следующее std::vector<int> для работы:

std::vector<int> vec;
vec.push_back(4);
vec.push_back(-8);
vec.push_back(1);
vec.push_back(0);
vec.push_back(7);

Очевидно, вы захотите разрешить следующий вариант использования:

std::for_each(vec.cbegin(), vec.cend(), _1);

Но как это допустить?Сначала вам нужно определить _1, а затем вам нужно будет реализовать перегрузку «все идет» оператора вызова функции для типа _1.

Способ, которым Boost Lambda и BoostПривязка определяет объекты-заполнители _1, _2, ... для придания им фиктивного типа.Например, объект _1 может иметь тип placeholder1_t:

struct placeholder1_t { };
placeholder1_t _1;

struct placeholder2_t { };
placeholder2_t _2;

Такой «фиктивный тип» часто неофициально называют типом тега .Существует много библиотек C ++ и, действительно, STL, которые используют типы тегов (например, std::nothrow_t).Они используются для выбора «правильной» перегрузки функции для выполнения.По сути, создаются фиктивные объекты, имеющие тип тега, и они передаются в функцию.Функция никак не использует фиктивный объект (фактически, большую часть времени имя параметра даже не указывается для него), но благодаря наличию этого дополнительного параметра компилятор может выбрать правильную перегрузку для вызова.

Давайте расширим определение placeholder1_t, добавив перегрузки оператора вызова функции.Помните, что мы хотим, чтобы он принимал все что угодно, поэтому перегрузки оператора вызова функции сами будут определяться типом аргумента:

struct placeholder1_t
{
    template <typename ArgT>
    ArgT& operator()(ArgT& arg) const {
        return arg;
    }

    template <typename ArgT>
    const ArgT& operator()(const ArgT& arg) const {
        return arg;
    }
};

Вот и все!Наши простейшие сценарии использования теперь будут компилироваться и запускаться:

std::for_each(vec.cbegin(), vec.cend(), _1);

Конечно, в основном это равносильно запрету.

Давайте теперь поработаем над _1 + 5.Что должно это выражение делать ?Он должен возвращать унарный функциональный объект, который при вызове с аргументом (некоторого неизвестного типа) приводит к тому аргументу плюс 5. Делая это более универсальным, выражение имеет вид unary-функциональный-объект + объект .Возвращенный объект сам по себе является унарным функциональным объектом.

Тип возвращаемого объекта должен быть определен.Это будет шаблон с двумя параметрами типа шаблона: унарный функциональный тип и тип объекта, который добавляется к результату унарного функционала:

template <typename UnaryFnT, typename ObjT>
struct unary_plus_object_partfn_t;

«partfn» относится к функциональному типупредставляет частичное применение бинарного оператора +.Экземплярам этого типа требуется копия унарного функционального объекта (имеющего тип UnaryFnT) и другого объекта (имеющего тип ObjT):

template <typename UnaryFnT, typename ObjT>
struct unary_plus_object_partfn_t
{
    UnaryFnT m_fn;
    ObjT m_obj;

    unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj)
        : m_fn(fn), m_obj(obj)
    {
    }
};

Хорошо.Оператор вызова функции также должен быть перегружен, чтобы учесть любой аргумент.Мы будем использовать функцию C ++ 11 decltype для ссылки на тип выражения, так как мы не знаем, что это такое заранее:

template <typename UnaryFnT, typename ObjT>
struct unary_plus_object_partfn_t
{
    UnaryFnT m_fn;
    ObjT m_obj;

    unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj)
        : m_fn(fn), m_obj(obj)
    {
    }

    template <typename ArgT>
    auto operator()(ArgT& arg) const -> decltype(m_fn(arg) + m_obj) {
        return m_fn(arg) + m_obj;
    }

    template <typename ArgT>
    auto operator()(const ArgT& arg) const -> decltype(m_fn(arg) + m_obj) {
        return m_fn(arg) + m_obj;
    }
};

Это начинает усложняться, но естьникаких сюрпризов в этом коде.По сути, это говорит о том, что оператор вызова функции перегружен, чтобы принимать практически любой аргумент.Затем он вызовет m_fn (унарный функциональный объект) для аргумента и добавит m_obj к результату.Тип возвращаемого значения - это decltype для m_fn(arg) + m_obj.

. Теперь, когда тип определен, мы можем записать перегрузку двоичного оператора +, принимающего объект типа placeholder1_t слева:

template <typename ObjT>
inline unary_plus_object_partfn_t<placeholder1_t, ObjT> operator+(const placeholder1_t& fn, ObjT obj)
{
    return unary_plus_object_partfn_t<placeholder1_t, ObjT>(fn, obj);
}

Теперь мы можем скомпилировать и запустить второй вариант использования:

std::transform(vec.cbegin(), vec.cend(), std::ostream_iterator<int>(std::cout, " "), _1 + 5);
std::cout << std::endl;

, который выдает:

9 -3 6 5 12

Это в основном все, что вам нужно сделатьрешить проблему.Подумайте, как можно написать пользовательские функциональные типы, экземпляры которых могут быть возвращены перегрузками операторов.

РЕДАКТИРОВАТЬ: Улучшена перегрузка операторов вызова функций благодаря использованию передачи по ссылке.

EDIT2: В некоторых случаях необходимо хранить ссылку на объект, а не его копию.Например, чтобы вместить std::cout << _1, вам потребуется сохранить ссылку на std::cout в результирующем функциональном объекте, поскольку конструктор копирования std::ios_base является закрытым, и невозможно копировать объекты конструирования любого класса, производного от std::ios_base включая std::ostream.

Чтобы разрешить std::cout << _1, вы можете написать шаблон ref_insert_unary_partfn_t.Такой шаблон, как пример unary_plus_object_partfn_t выше, будет создан на основе типа объекта и унарного функционального типа:

template <typename ObjT, typename UnaryFnT>
struct ref_insert_unary_partfn_t;

Экземплярам экземпляров этого шаблона потребуется хранить ссылку на объекттипа ObjT, а также копия унарного функционального объекта типа UnaryFnT:

template <typename ObjT, typename UnaryFnT>
struct ref_insert_unary_partfn_t
{
    ObjT& m_ref;
    UnaryFnT m_fn;

    ref_insert_unary_partfn_t(ObjT& ref, UnaryFnT fn)
        : m_ref(ref), m_fn(fn)
    {
    }
};

Добавить перегрузки оператора вызова функции, как и ранее, а также перегрузки оператора вставки, <<.

В случае std::cout << _1 возвращаемый объект будет иметь тип ref_insert_unary_partfn_t<std::basic_ostream<char>, placeholder1_t>.

2 голосов
/ 09 декабря 2011

Простой пример:

template <typename T>
class Parameter
{
};

template <typename T>
struct Ascending
{
    bool operator()(T left, T right)
    {
        return left < right;
    }
};

template <typename T>
Ascending<T> operator > (Parameter<T> p1, Parameter<T> p2)
{
    return Ascending<T>();
}

int main()
{

    std::vector<int> vec;
    vec.push_back(3);
    vec.push_back(6);
    vec.push_back(7);
    vec.push_back(2);
    vec.push_back(7);

    std::vector<int>::iterator a = vec.begin();

    Parameter<int> _1;
    Parameter<int> _2;

    std::sort(a, a+4, _1 > _2);
}
...