Function that accepts both lvalue and rvalue arguments

寵の児 提交于 2019-11-27 01:46:21

问题


Is there a way to write a function in C++ that accepts both lvalue and rvalue arguments, without making it a template?

For example, suppose I write a function print_stream that reads from an istream and prints the data that was read to the screen, or something.

I think it's reasonable to call print_stream like this:

fstream file{"filename"};
print_stream(file);

as well as like this:

print_stream(fstream{"filename"});

But how do I declare print_stream so that both uses work?

If I declare it as

void print_stream(istream& is);

then the second use won't compile because an rvalue will not bind to a non-const lvalue reference.

If I declare it as

void print_stream(istream&& is);

then the first use won't compile because an lvalue will not bind to an rvalue reference.

If I declare it as

void print_stream(const istream& is);

then the function's implementation won't compile because you can't read from a const istream.

I can't make the function a template and use a "universal reference", because its implementation needs to be separately compiled.

I could provide two overloads:

void print_stream(istream& is);
void print_stream(istream&& is);

and have the second call the first, but that seems like a lot of unnecessary boilerplate, and I would find it very unfortunate to have to do that every time I write a function with semantics like this.

Is there something better I can do?


回答1:


There is not much of a sane choice other than offering two overloads or making your function a template, I would say.

If you really, really need an (ugly) alternative, then I guess the only (insane) thing you can do is to have your function accept a const&, with a pre-condition saying that you cannot pass an object of a const-qualified type to it (you don't want to support that anyway). The function would then be allowed to cast away the constness of the reference.

But I'd personally write two overloads and define one in terms of the other, so you do duplicate the declaration, but not the definition:

void foo(X& x) 
{ 
    // Here goes the stuff... 
}

void foo(X&& x) { foo(x); }



回答2:


Another rather ugly alternative is to make the function a template and explicitly instantiate both versions:

template<typename T>
void print(T&&) { /* ... */ }

template void print<istream&>(istream&);
template void print<istream&&>(istream&&);

This can be compiled separately. The client code only needs the declaration of the template.

I'd personaly just stick with what Andy Prowl suggests, though.




回答3:


Be bold, embrace generic forward functions and name them well.

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"};}

Note that this will work with anything that will make sense for it to work, not only ostreams. That is a good thing.

If the function is called with an argument that doesn't make sense, it will simply ignore this definition. Incidentally, this works better if indentation is set to 4 spaces. :)


This is the same as Cube's answer, except that I am saying that it is, when possible, more elegant to not check for specific types and let generic programming do its thing.




回答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's an alternative choice, and i think is's 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:


If I expect the function to take ownership of the argument of the function, I tend to put the argument as a value, and then move it in. This is not desirable if the argument is expensive to move (e.g. std::array).

A typical example is setting an object's string member:

class Foo {
   private:
      std::string name;
   public:
      void set_name( std::string new_name ) { name = std::move(new_name); }
};

With this definition of the function, I can call set the name with no copies of the string object:

Foo foo;
foo.set_name( std::string("John Doe") );
// or
std::string tmp_name("Jane Doe");
foo.set_name( std::move(tmp_name) );

But I can create a copy it if I want to keep the ownership of the original value:

std::string name_to_keep("John Doe");
foo.set_name( name_to_keep );

This last version would have very similar behavior to passing const reference and making a copy assignment:

class Foo {
   // ...
   public:
      void set_name( const std::string& new_name ) { name = new_name; }
};

This is specially useful for constructors.



来源:https://stackoverflow.com/questions/17644133/function-that-accepts-both-lvalue-and-rvalue-arguments

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!