Использование ссылок 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 5

2 ответа:

void Foo(Bar&& b = Bar()) { /* stuff */ }
Это, конечно, допустимое использование ссылок на значения r, но оно никоим образом не отражает фактическую семантику и, таким образом, "нарушается конструкцией".

То, что вы хотите сделать, это использовать функцию пересылки, предоставляющую аргумент по умолчанию следующим образом:

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 */ }