Comments 5
Не совсем понятен вопрос, в чём преимущество перед диапазонами? Вы сами про них упоминаете, но в чём их минусы? Почему бы не пользоваться ими?
Там компонуемость лучше, написание функций проще и без макросов. Тот же пример из документации:
std::vector<int> vi{1,2,3,4,5,6,7,8,9,10};
using namespace ranges;
auto rng = vi | view::remove_if([](int i){return i % 2 == 1;})
| view::transform([](int i){return std::to_string(i);});
// rng == {"2","4","6","8","10"};
Ничто не мешает пользоваться ими уже сейчас. Да, не в std, но ranges-v3 уже работающая header-only имплементация. Принести её в проект ничего не стоит.
Спасибо за вопрос. Во вступлении я об этом упоминал, но не слишком подробно.
Я ни в коем случае не утверждаю, что есть какие-то преимущества перед диапазонами. Наоборот, всё, что здесь описано, как раз и родилось из активного использования диапазонов и адаптеров.
// Полупсевдокод
struct Class
{
std::string name;
std::size_t index;
};
std::vector<Class> v{...};
const auto r =
ranges::view::zip(v, ranges::view::iota(0ul))
| ranges::view::transform(make_from_tuple<Class>)
| ranges::to_vector
| ranges::action::sort(each(MEMBER(name)) | std::greater<>{});
В данной публикации я хотел описать именно технику. А поскольку техника сама по себе не требует работы именно с диапазонам, то, чтобы не вводить новых сущностей, я пользовался исключительно стандартной библиотекой.
Интересная статья, спасибо за труд. Объясните пожалуйста, зачем вы здесь вызываете std::move
?
template <typename ... Ts>
constexpr decltype(auto) operator () (Ts && ... ts) &&
{
return std::move(l)(std::move(r)(std::forward<Ts>(ts)...));
}
С удовольствием.
Факт №1
Методы классов в языке C++ могут перегружаться по категории экземпляра класса:
struct Class
{
void f () const &; // Вызывается, когда экземпляр является ссылкой на константу.
void f () &; // Вызывается, когда экземпляр является ссылкой.
void f () &&; // Вызывается, когда экземпляр является ссылкой на rvalue.
};
https://wandbox.org/permlink/Og3kWTDeZFMj0ncP
Факт №2
Функциональный объект может быть классом, обладающим нетривиальным состоянием и имеющим перегрузки оператора "скобки", описанные выше.
Суть
Теперь реализуем такой функциональный объект с состоянием и перегрузками оператора вызова.
Пусть это будет функциональный объект, который хранит некоторую строчку, в оператор вызова принимает контейнер, в который он эту строчку записывает:
template <typename T>
struct push_back_fn
{
T value;
template <typename Container>
void operator () (Container & c) const &
{
c.push_back(value);
}
template <typename Container>
void operator () (Container & c) &
{
c.push_back(value);
}
template <typename Container>
void operator () (Container & c) &&
{
// Наш объект является `rvalue`, а значит, скоро закочит своё
// существование. Следовательно, строчка ему больше не понадобится,
// так что владение строчкой можно отдать контейнеру.
c.push_back(std::move(value)); // <--
}
};
template <typename T>
auto push_back (T && t)
-> push_back_fn<std::decay_t<T>>
{
return {std::forward<T>(t)};
}
Теперь использование:
int main ()
{
// Сохранили экземпляр нашего функционального объекта. Он `lvalue`.
auto pb = push_back(std::string("qwerty"));
// Записали копию строки в конец контейнера.
std::vector<std::string> v;
pb(v);
// Записали ещё одну копию той же строки в коней другого контейнера.
std::vector<std::string> w;
pb(w);
// А здесь наш `push_back_fn` вызывается как `rvalue`,
// поэтому ни одного копирования не произошло.
push_back(std::string("asdfgh"))(v);
}
https://wandbox.org/permlink/MK2gIPzT3TOWBlw5
Исходя из всего вышеперечисленного, при композиции нужно обеспечить правильные вызовы каждого функционального объекта, чтобы не производить лишние действия в том случае, когда этого можно избежать.
Элементы функционального программирования в C++: композиции отображений