2017-02-21 3 views
6

Мне очень нравится использовать cmcstl2, реализацию диапазонов TS. Мне особенно нравятся необязательные проекции на каждый STL-алгоритм. Invocable типы получить переадресованы (ЭУ ... или нет), как это: (min_element.hpp)Как правильно переадресовывать Invocable типы

template <ForwardIterator I, Sentinel<I> S, 
    class Comp = less<>, class Proj = identity> 
requires 
    IndirectStrictWeakOrder< 
     Comp, projected<I, Proj>>() 
I min_element(I first, S last, Comp comp = Comp{}, Proj proj = Proj{}); 

template <ForwardRange Rng, class Comp = less<>, class Proj = identity> 
requires 
    IndirectStrictWeakOrder< 
     Comp, projected<iterator_t<Rng>, Proj>>() 
safe_iterator_t<Rng> 
min_element(Rng&& rng, Comp comp = Comp{}, Proj proj = Proj{}) 
{ 
    return __stl2::min_element(__stl2::begin(rng), __stl2::end(rng), 
     __stl2::ref(comp), __stl2::ref(proj)); 
} 

Как Справочно: range-v3 библиотека реализует это следующим образом: (min_element.hpp)

struct min_element_fn { 
     template<typename I, typename S, typename C = ordered_less, typename P = ident, 
      CONCEPT_REQUIRES_(ForwardIterator<I>() && Sentinel<S, I>() && 
       IndirectRelation<C, projected<I, P>>())> 
     I operator()(I begin, S end, C pred = C{}, P proj = P{}) const; 

     template<typename Rng, typename C = ordered_less, typename P = ident, 
      typename I = range_iterator_t<Rng>, 
      CONCEPT_REQUIRES_(ForwardRange<Rng>() && 
       IndirectRelation<C, projected<I, P>>())> 
     range_safe_iterator_t<Rng> operator()(Rng &&rng, C pred = C{}, P proj = P{}) const 
     { 
      return (*this)(begin(rng), end(rng), std::move(pred), std::move(proj)); 
     } 
}; 

Сейчас я стараюсь понять разницу и аргументацию обоих подходов. Почему я должен принимать Invocable типы по стоимости в любом случае? Почему я не должен использовать идеальную пересылку для этих типов?

Я понимаю второй подход больше, чем для первого, так как я понимаю методологию принятия аргументов погружения по значению.

ответ

8

Две причины:

  1. Мое чтение стандартной спецификации библиотеки является то, что алгоритмы могут копировать пользовательские функции и объекты, как столько раз, сколько им нравится, но указаны для выполнения всех вызовов на одном экземпляре. Поскольку cmcstl2 часто реализует алгоритмы в терминах других алгоритмов, самым простым способом удовлетворить это требование является внутреннее пропускание объектов функции на reference_wrapper. Например, binary_search вызывает lower_bound, а затем определяет, является ли элемент, обозначенный нижней границей, точным совпадением. Он передает reference_wrapper s объектам сравнения и объекта проекта в lower_bound, чтобы он мог вызывать те же экземпляры позже.

  2. Большие и/или изменяемые объекты функции могут быть редкими, но нет причин, по которым они должны быть плохо поддерживаются в стандартной библиотеке. Копирование обычно дешево, и перемещение почти всегда происходит, но передача по ссылке «никогда» не стоит дорого. cmcstl2 минимизирует обе копии перемещений объектов пользовательских функций. (Воздушные кавычки на «никогда» не указывают, что передача по ссылке приводит к значительно более тяжелой нагрузке на оптимизатор, увеличивая время компиляции и потенциально генерируя плохой код в угловых случаях, если анализ псевдонимов путают ссылки на функциональные объекты.)

В этих рассуждениях есть некоторые очевидные недостатки. Прежде всего мне: «Если функциональные объекты могут быть полезны с точки зрения состояния, не должны ли алгоритмы возвращать их для сохранения этого состояния, как и std::for_each?» Дизайн cmcstl2 по существу нарушает то, что Elements of Programming называет «Закон полезного возвращения». Должны ли мы усложнять подписи стандартных алгоритмов для возврата целых трех функциональных объектов - скажем, компаратора и двух прогнозов - для размещения 0,1% -ного варианта использования?Я думаю, что очевидный ответ здесь «нет», особенно учитывая, что обходной путь настолько прост: передайте reference_wrapper.

Итак, почему cmcstl2 вообще - и Standard C++ 's std::for_each в частности - выйти из своего способа размещения больших и/или изменяемых функциональных объектов, когда обходной путь так же это передать reference_wrapper? Кажется, дизайнер cmcstl2 совершил ту же ошибку здесь, что и LWG, когда они сделали std::for_each, возвращая свой объект функции.

+1

Источник: am автор cmcstl2. – Casey

+0

'reference_wrapper' не так легко записать, когда вы передаете большой/изменяемый временный. Я также не думаю, что 1) соответствует чтению других исполнителей, хотя это приятно иметь. –

+0

Я не ожидал получить другой ответ, но я рад. Особенно пример бинарного поиска действительно хорош. – Maikel

7

Традиционно принимать Invocable по значению, потому что они имеют небольшой размер sizeof, например, указателем на функцию или лямбдой с несколькими захватами. Такие функциональные параметры, в соответствии с ABI, передаются в машинные регистры или полностью исключаются в случае less или identity. С другой стороны, передача по ссылке имеет тенденцию подталкивать компилятор для размещения реального объекта в ОЗУ.

Более крупные объекты, или объекты со значительным изменчивым состоянием, могут быть переданы через std::ref. Получаемый std::reference_wrapper тривиально-копируемый и такой же большой, как указатель, поэтому он эффективно передается по значению.

+0

Так что это просто вопрос вкуса: использовать ли 'std :: ref' для' std :: move' для этих типов? Есть ли смысл копировать объект с функцией состояния? Будет ли это рассматриваться как побочный эффект при изменении такого состояния, тогда как STL получил функциональные корни? – Maikel

+2

1. Для названных объектов да. Но обычно аргумент является выражением или '{}'. 2. Зависит от того, что вы подразумеваете под контролем состояния. Лямбда с '[&]' захватами является сдержанной, но неизменной. Многие снимки могут ухудшать производительность с помощью пропускной способности (или, может быть, в зависимости от оптимизации). Лямбда с 'mutable' может ошибочно ошибиться при копировании, если этого не произойдет. 3. Функциональная философия, возможно, помогла STL игнорировать самонастраивающиеся функторы, но также они просто необычны. Ссылочная семантика, возможно, слишком распространена в C++; это немного необычно для библиотеки по умолчанию, но она по-прежнему хорошая конструкция. – Potatoswatter

+2

Я автор range-v3, и я одобряю это сообщение. :-) –

Смежные вопросы