Использование ссылок rvalue для аргументов по умолчанию
Я хочу создать функцию, которая берет необязательную ссылку на объект и создает ее на время действия функции, если она не предусмотрена, т. е.
void Foo(Bar& b = Bar()) { /* stuff */ }
Это, конечно, недопустимый код, поскольку Bar
не может быть неявно преобразован в ссылку Bar
в этом контексте. Ссылка не может быть const
, так как b
мутирует внутри функции.
Вы можете обойти это, используя ссылку rvalue, т. е.
void Foo(Bar&& b = Bar()) { /* stuff */ }
Является ли это допустимым использованием ссылок rvalue? Вызывающие теперь должны вызывать std::move
по своим аргументам Bar
, хотя у меня нет намерения очищать переданные Bar
, как это обычно бывает, когда вы передаете rvalues.
2 ответа:
Это, конечно, допустимое использование ссылок на значения r, но оно никоим образом не отражает фактическую семантику и, таким образом, "нарушается конструкцией".void Foo(Bar&& b = Bar()) { /* stuff */ }
То, что вы хотите сделать, это использовать функцию пересылки, предоставляющую аргумент по умолчанию следующим образом:
void Foo(Bar& b) { /* stuff */ } void Foo() { Bar b{}; Foo(b); }
Или используйте статический аргумент по умолчанию (помните, что он всегда повторно использует один и тот же Объект):
template<class T> decltype(T{})& default_object() {static T x{}; return x;} void Foo(Bar& b = default_object<Bar>()) { /* stuff */ }
Или как предлагает KerrekSB в комментарии (я добавил
constexpr
) Используйте этотопасный шаблон функция:template<class T> constexpr T& no_move(T&& t) { return t; } void Foo(Bar& b = no_move(Bar{})) { /* stuff */ }
Таким образом, у вас есть функция, которая принимает параметр in-out, который она собирается изменить, чтобы передать обратно информацию вызывающему объекту.
Но вы хотите, чтобы параметр был необязательным.
Таким образом, ваше решение состоит в том, чтобы заставить параметр казаться параметром in, требуя от вызывающих объектов переместить аргументы (что обычно означает, что они потеряли любое состояние, которое у них было или может быть в неопределенном состоянии). Это плохой, очень плохой дизайн. Вы будете путать абонентов со странным API, созданным для удобства внутренней реализации функции. Вы должны разрабатывать API для пользователей, а не для разработчиков.Вы можете либо сделать то, что предлагает Дедупликатор, и разделить его на две функции, одна из которых предоставляет фиктивный объект, предоставляемый в качестве параметра in-out, а затем отбрасывается:
void Foo(Bar& b) { /* stuff */ } void Foo() { Bar dummy{}; Foo(dummy); }
Или Поскольку вы, кажется, хотите получить ссылку, которая может быть нулевой, прекратите использовать ссылку и используйте правильную языковую функцию для передачи чего-то по ссылке, которая может быть нулевой:
void Foo(Bar* b) { /* stuff, updating b if not null */ }