Подтвердить что ты не робот

Функция, которая принимает аргументы 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);

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

Есть ли что-то лучшее, что я могу сделать?

4b9b3361

Ответ 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; }
};

Это особенно полезно для конструкторов.