Основное руководство Cpp F19 сообщает нам
Отметьте функцию, которая принимает параметр TP&& (где TP — имя параметра типа шаблона) и делает с ним что-либо, кроме std::пересылки ровно один раз по каждому статическому пути.
Буквально это означает что-то вроде
template<typename R, typename V> bool contains(R&& range, const V& value)
{
return std::find(range, value) != range.end();
}
Должен быть помечен, потому что std::forward
не вызывался (и даже если бы он был вызван, он бы вызвал .end()
).
Но, насколько я понимаю, эта реализация является правильной и разумной.
Мне нужна универсальная ссылка R&&
, потому что в реальном мире она нужна мне для привязки к значениям const&
(1, см. ниже), но она также нужна мне для привязки к значениям.
который не может стать константой (потому что begin()
и end()
не являются константами) (2).
Если бы я сменил подпись на contains(const R& range, const V& value)
, она была бы несовместима с большинством просмотров.
int main()
{
const std::vector<int> vs{1, 2, 3, 4};
std::cout << contains(vs, 7); // (1)
std::cout << contains(vs | std::views::transform(squared), 7); // (2)
}
Вопрос: F19 упускает какой-то крайний случай или я упускаю какую-то деталь?
Возможная реализация std::contains также передается R
как универсальная ссылка без вызова std::forward
.
Альтернатива содержит реализацию с использованием std::forward
(но также и .end()
, следовательно, она также не совместима с F19):
template<typename R, typename V> bool contains(R&& range, const V& value)
{
const auto& end = range.end();
return std::find(std::forward<R>(range), value) != end;
}
Кажется, это правило касается именно следующего случая: если объект должен быть передан в другой код и не использоваться этой функцией напрямую, мы хотим сделать эту функцию независимой от аргументов const-ness и rvalue-ness. Хотя Я не вижу какого-либо правила, описывающего нужный вам случай - функция игнорирует const
-ность, но использует значение.
Руководство не является неправильным, бывают случаи, когда приведение rvalue к lvalue может быть ошибкой, ваш код не является примером такого кода. Если вы будете следовать рекомендациям, вы можете уменьшить количество ошибок, но несоблюдение их не приводит к возникновению ошибок. , поэтому они являются рекомендациями, а не стандартными правилами языка.
Вы уверены, что ваш код компилируется?
что бы вы добавили в руководство, чтобы оно не соответствовало вашему случаю, но все же соответствовало случаю, когда кто-то просто забыл std::forward
? Как заявляли другие, руководство направлено не на исключение любого кода, который не соответствует этому руководству, а на то, чтобы помочь диагностировать распространенные ошибки с помощью руководства (а это обязательно приводит к ложным срабатываниям).
другими словами, хорошее руководство — это такое, которое с высокой вероятностью идентифицирует неправильный фрагмент кода. Это фокус. Наличие другого неправильного кода, который не указан в руководстве, неизбежно, а наличие правильного кода, который указан в руководстве, является компромиссом. Неработающий код обычно обходится намного дороже, чем программист, который проверяет свой код, чтобы убедиться, что он «в порядке», они все равно должны были это сделать;)
@n.m.couldbeanAI Я уверен, что он не скомпилируется, потому что для краткости я пропустил некоторые вещи (например, #includes). Это не должно было быть MRE, а скорее псевдокодом, передающим какую-то мысль. Могу ли я внести ясность в вопрос, исправив опечатки или что-то подобное?
Да.
Возможно, проблема здесь в том, что ссылки на пересылку изначально были введены для одной конкретной цели: пересылки. Поэтому в итоге мы им позвонили пересылали рекомендации. В этом контексте, безусловно, имеет смысл задаться вопросом об использовании ссылки пересылки, которая не пересылается. И действительно, это, безусловно, наиболее распространенный вариант использования ссылок на пересылку.
Однако ссылки пересылки также служат другой цели — еще одной встроенной в их первоначальное имя: универсальной ссылке. Если вы сегодня хотите написать функцию, которая может принимать либо lvalue, либо rvalue, у вас есть только три варианта:
template <class T> void f(T a);
— это может быть то, что вы хотите, но для этого требуется копирование lvalue и принудительное перемещение значений x.template <class T> void g(T const& b);
— это хороший выбор по умолчанию, перемещение или копирование не происходит, но b
всегда есть const
template <class T> void h(T&& c);
— перемещение или копирование не происходит, c
может быть изменчивым.Если вы хотите избежать копирования/перемещения и вам необходимо сохранить const
-ность, единственный вариант — использовать ссылку пересылки. Что в данном контексте лучше назвать универсальной ссылкой. Но в любом случае это один и тот же синтаксис. Диапазоны активно используют этот паттерн – и в этом паттерне нет ничего плохого. Это инструмент, доступный в нашем распоряжении для решения этой проблемы.
Вы можете себе представить, что, возможно, если бы у нас было что-то вроде метки параметра, мы могли бы различать контексты, в которых мы просто хотим иметь универсальную ссылку, и где мы хотим фактически пересылать данные. Но за исключением чего-то подобного, вариант использования «универсальной ссылки» для пересылки ссылок вполне допустим. Мы просто не можем синтаксически отличить случаи, когда мы намеревались пересылать, но забыли это сделать (которые следует пометить), и случаи, когда мы не собирались пересылать и не должны (которые не следует помечать).
Да, это правило звучит как бред. Основные рекомендации не идеальны.