Функция, которая принимает аргументы lvalue и rvalue
Есть ли способ написать функцию в С++, которая принимает как аргументы lvalue, так и rvalue, не делая ее шаблоном?
Например, предположим, что я пишу функцию print_stream
, которая читает из istream
и печатает данные, которые были прочитаны на экране, или что-то в этом роде.
Я думаю, что разумно называть print_stream
следующим образом:
fstream file{"filename"};
print_stream(file);
а также вот так:
print_stream(fstream{"filename"});
Но как объявить print_stream
так, чтобы оба использования работали?
Если я объявляю его как
void print_stream(istream& is);
то второе использование не будет скомпилировано, потому что rvalue не будет связываться с неконсольной ссылкой lvalue.
Если я объявляю его как
void print_stream(istream&& is);
то первое использование не будет компилироваться, потому что lvalue не будет привязываться к ссылке rvalue.
Если я объявляю его как
void print_stream(const istream& is);
тогда реализация функции не будет компилироваться, потому что вы не можете читать из const istream
.
Я не могу сделать функцию шаблоном и использовать "универсальную ссылку", потому что ее реализация должна быть скомпилирована отдельно.
Я мог бы предоставить две перегрузки:
void print_stream(istream& is);
void print_stream(istream&& is);
и второй вызов первый, но это похоже на много ненужного шаблона, и мне было бы очень неприятно это делать каждый раз, когда я пишу функцию с семантикой вроде этого.
Есть ли что-то лучшее, что я могу сделать?
Ответы
Ответ 1
Существует не так уж много здравого выбора, кроме предложения двух перегрузок или создания вашей функции шаблона, я бы сказал.
Если вам действительно нужна настоящая альтернатива, я полагаю, что единственная (безумная) вещь, которую вы можете сделать, - это принять вашу функцию const&
с предварительным условием, говорящим, что вы не можете передать объект от const
-qualified типа к нему (вы все равно не хотите его поддерживать). Функция затем будет позволена отбрасывать const
значение ссылки.
Но я лично написал две перегрузки и определял бы одну в терминах другой, поэтому вы дублируете декларацию, но не определение:
void foo(X& x)
{
// Here goes the stuff...
}
void foo(X&& x) { foo(x); }
Ответ 2
Другой довольно уродливой альтернативой является создание функции шаблоном и явное создание экземпляров обеих версий:
template<typename T>
void print(T&&) { /* ... */ }
template void print<istream&>(istream&);
template void print<istream&&>(istream&&);
Это может быть скомпилировано отдельно. Клиентскому коду требуется только объявление шаблона.
Я бы лично придерживался того, что предлагает Энди Проул.
Ответ 3
Будь смелым, обними общие передовые функции и назови их хорошо.
template<typename Stream>
auto stream_meh_to(Stream&& s)
->decltype(std::forward<Stream>(s) << std::string{ }){
return std::forward<Stream>(s) << std::string{"meh\n"};}
Обратите внимание, что это будет работать со всем, что будет иметь смысл для работы, а не только с ostream
. Это хорошая вещь.
Если функция вызывается с аргументом, который не имеет смысла, она просто игнорирует это определение. Кстати, это работает лучше, если для отступа задано 4 пробела. :)
Это то же самое, что и ответ Cube, за исключением того, что я говорю, что, когда это возможно, более элегантно не проверять конкретные типы и позволить универсальному программированию делать свое дело.
Ответ 4
// Because of universal reference
// template function with && can catch rvalue and lvalue
// We can use std::is_same to restrict T must be istream
// it an alternative choice, and i think is better than two overload functions
template <typename T>
typename std::enable_if<
std::is_same<typename std::decay<T>::type, istream>::value
>::type
print(T&& t) {
// you can get the real value type by forward
// std::forward<T>(t)
}
Ответ 5
Если я ожидаю, что функция получит владение аргументом функции, я склонен поместить аргумент в качестве значения, а затем переместить его. Это нежелательно, если аргумент является дорогим для перемещения (например, std :: array).
Типичным примером является установка члена строки объекта:
class Foo {
private:
std::string name;
public:
void set_name( std::string new_name ) { name = std::move(new_name); }
};
С этим определением функции я могу вызвать set name без копий строкового объекта:
Foo foo;
foo.set_name( std::string("John Doe") );
// or
std::string tmp_name("Jane Doe");
foo.set_name( std::move(tmp_name) );
Но я могу создать его копию, если я хочу сохранить право собственности на первоначальное значение:
std::string name_to_keep("John Doe");
foo.set_name( name_to_keep );
Эта последняя версия будет очень похожа на передачу константной ссылки и создание копии:
class Foo {
// ...
public:
void set_name( const std::string& new_name ) { name = new_name; }
};
Это особенно полезно для конструкторов.