接受左值和右值参数的函数

问题描述 投票:30回答:5

有没有办法在C ++中编写一个接受左值和右值参数的函数,而不是模板?

例如,假设我编写了一个从print_stream读取的函数istream,并将读取的数据打印到屏幕上,或者其他内容。

我认为像这样调用print_stream是合理的:

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

以及像这样:

print_stream(fstream{"filename"});

但是,如何宣布print_stream以便两种用途都有效?

如果我声明它

void print_stream(istream& is);

然后第二次使用将无法编译,因为rvalue不会绑定到非const左值引用。

如果我声明它

void print_stream(istream&& is);

然后第一次使用将无法编译,因为左值不会绑定到右值引用。

如果我声明它

void print_stream(const istream& is);

那么函数的实现将无法编译,因为你无法读取const istream

我不能使该函数成为模板并使用“通用引用”,因为它的实现需要单独编译。

我可以提供两个重载:

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

并且第二次调用第一次,但这似乎是很多不必要的样板,并且我发现每次用这样的语义编写函数时都必须这样做非常不幸。

我能做些什么吗?

c++ function c++11 lvalue rvalue
5个回答
18
投票

我想说,除了提供两个重载或使你的功能成为模板之外,没有太多合理的选择。

如果你真的,真的需要一个(丑陋的)替代品,那么我猜你唯一的(疯狂的)事情就是让你的函数接受一个const&,前提是你不能传递一个const资格的对象键入它(你不想支持它)。然后允许该函数抛弃引用的constness。

但我个人会写两个重载并根据另一个定义一个,所以你复制了声明,但不是定义:

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

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

6
投票

另一个相当丑陋的选择是使函数成为模板并显式实例化两个版本:

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

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

这可以单独编译。客户端代码只需要声明模板。

不过,我个人只是坚持Andy Prowl的建议。


4
投票

要大胆,接受通用的前向功能,并将它们命名为。

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

请注意,这将适用于任何有意义的工作,而不仅仅是ostreams。这是一件好事。

如果使用没有意义的参数调用函数,它将忽略此定义。顺便提一下,如果将缩进设置为4个空格,则效果会更好。 :)


这与Cube的答案相同,只是我说在可能的情况下,更优雅的是不检查特定类型并让泛型编程做它的事情。


3
投票
// 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)
}

0
投票

如果我希望函数取得函数参数的所有权,我倾向于将参数作为一个值,然后将其移入。如果参数移动很昂贵(例如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 );

最后一个版本与传递const引用和进行复制赋值的行为非常相似:

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

这对构造函数特别有用。

© www.soinside.com 2019 - 2024. All rights reserved.