Почему std:: find if(first, last, p) не принимает предикат по ссылке?
Я смотрел на различные подписи для std::find_if
ВКЛ cppreference.com, и я заметил, что ароматы, которые принимают функцию предиката, по-видимому, принимают ее по значению:
template< class InputIt, class UnaryPredicate >
InputIt find_if( InputIt first, InputIt last,
UnaryPredicate p );
Если я правильно их понимаю, лямбды с захваченными переменными выделяют хранилище для ссылок или копий своих данных, и поэтому, по-видимому, "pass-by-value" будет означать, что копии захваченных данных копируются для вызова.
С другой стороны, для указатели функций и другие непосредственно адресуемые вещи, производительность должна быть лучше, если указатель функции передается напрямую, а не по ссылке на указатель (указатель на указатель).
во-первых, это правильно? Это UnaryPredicate
выше будет значение параметра?
во-вторых, правильно ли мое понимание прохождения лямбд?
В-третьих, есть ли причина для передачи по значению, а не по ссылке в этой ситуации? И более того, там нет достаточно неоднозначного синтаксиса (привет, универсальная ссылка), который позволил бы компилятору делать все, что он хочет, чтобы получить максимальную производительность?
2 ответов
является ли UnaryPredicate выше будет параметром по значению?
Да, вот что он говорит в списке параметров функции. Он принимает тип выведенного значения.
помимо этого, лямбда-выражения являются prvalues. Значит, с c++17гарантированная копия elision, что p
инициализируется напрямую из лямбда-выражения. Никакие дополнительные копии закрытия или захваченных объектов не производятся при его передаче в функцию (функция может сделать больше копий внутри, однако, хотя это не распространено).
если предикат был передан по ссылке, необходимо материализовать временный объект. Таким образом, для лямбда-выражения коммутатор не получает ничего для передачи по ссылке.
если у вас есть другие виды предикатов, которые экспансивны для копирования, то вы можете передать std::reference_wrapper
к этому объекту предиката, для дешевого "дескриптора" к нему. Обертка operator()
поступлю правильно.
определение в основном исторический, но в настоящее время это действительно не проблема, чтобы сделать это с пропуском по стоимости.
чтобы уточнить, почему референтная семантика будет сосать, давайте попробуем взять его через годы. Простая ссылка lvalue не будет делать, так как теперь мы не поддерживаем привязку к rvalue. Ссылка const lvalue также не будет делать, так как теперь мы требуем, чтобы предикат не изменял никакого внутреннего состояния, и зачем?
до c++11, у нас нет альтернативы. Пропуск по значению будет лучше, чем ссылка. С новым стандартом, мы можем пересмотреть наш подход. Для поддержки rvalues мы можем добавить перегрузку ссылки rvalue. Но это упражнение в избыточности, поскольку ему не нужно делать ничего другого.
передавая значение, вызывающий имеет выбор в том, как его создать, и для prvalues, в c++17, это практически бесплатно. Если вызывающий так желает, они могут предоставить ссылочную семантику явно. Так что ничего не потеряно, и я думаю, что многое достигнуто с точки зрения простоты использования и дизайна API.
на самом деле есть несколько причин:
- вы всегда можете превратить выведенные аргументы значения в использование ссылочной семантики, но не вице-стих: просто передайте
std::ref(x)
вместоx
. 'std:: reference_wrapper не полностью эквивалентен передаче ссылки, но особенно для объекта функции он делает правильную вещь. То есть передача общих аргументов по значению является более общим подходом. - проходим по ссылке (
T&
) не работает для временного илиconst
объектыT const&
не работает для не-const&
, т. е. единственным выбором будетT&&
(ссылка для пересылки), которая не существовала до c++11, и интерфейсы алгоритмов не изменились с тех пор, как они были введены с C++98. - параметры значения можно скопировать elided не похож на любой вид параметров справки, включая ссылки препровождения.
в результате