完美的传递

cel*_*chk 13 c++ c++11

我正在考虑一个与完美转发有一些相似性的问题,但是函数参数没有传递给被调用函数,而是返回.这就是我称之为"完美传递"的原因.

问题如下:

假设我们有一个函数,它通过引用获取一个对象(可能还有一些额外的参数),修改该对象,并返回修改后的对象.这些函数的最着名的例子可能是operator<<operator>>iostreams一样.

让我们以iostream为例,因为它可以很好地展示我所追求的东西.例如,有时人们喜欢做的一件事是:

std::string s = (std::ostringstream() << foo << bar << baz).str();
Run Code Online (Sandbox Code Playgroud)

当然这不起作用,原因有两个:

  • std::ostringstream()是一个右值,但是operator<<将左值作为第一个参数

  • operator<<返回一个ostream&(以及,至少为标准的人实际上是一个basic_ostream<CharT, Traits>&其中CharTTraits从第一自变量推导出).

因此,我们假设我们要设计插入运算符,以便上述工作(您显然不能对现有运算符执行此操作,但您可以为自己的类执行此操作).显然,该解决方案应具有以下特征:

  • 第一个参数可以接受左值或右值.

  • 返回类型应该与传入的类型相同.但是它当然应该只接受ostreams(即从实例化派生的类basic_ostream).

虽然在这个特定用例中不需要,但我想添加第三个要求:

  • 如果第一个参数是rvalue,则返回值也是如此,否则返回左值.

这个额外的规则是你可以从通过函数传递的rvalue中移动构造(我不知道C++ 11流是否可移动构造,但它是一个更通用的方案,流如同方便的例子).

很明显,在C++ 03中,并不能满足这些要求.但是,在C++ 11中,我们有rvalue引用,这应该可以实现.

这是我的尝试:

#include <iostream>
#include <sstream>
#include <string>

template<typename Ostream> struct is_ostream
{
  typedef typename std::remove_reference<Ostream>::type candidate;
  typedef typename candidate::char_type char_type;
  typedef typename candidate::traits_type traits_type;
  typedef std::basic_ostream<char_type, traits_type> basic_ostream;
  static const bool value = std::is_base_of<basic_ostream, candidate>::value;
};

class SomeType {};

template<typename Ostream>
 typename std::enable_if<is_ostream<Ostream>::value, Ostream&&>::type
  operator<<(Ostream&& is, SomeType const& x)
{
  is << "SomeType";
  return std::forward<Ostream>(is);
}

int main()
{
  SomeType t;

  std::string s = (std::ostringstream() << t).str();

  std::cout << s << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

它确实用gcc编译(使用选项-std=c++0x),并在运行时SomeType按预期输出.然而,到达那里是一个非常复杂的机器.

因此我的问题:

  1. 我写的输出操作符确实按预期工作(并满足我给出的所有要求)?或者它可能以意想不到的方式失败或产生其他意外后果?特别是:我std::forward在这里使用正确吗?

  2. 有没有更简单的方法来满足要求?

  3. 假设这确实是正确的并且是最简单的方法:你认为这样做是个好主意,还是你会建议它(特别是对于我的例子中的流输出,以及作为传递对象的更一般方案)通过功能)?

How*_*ant 6

std::ostringstream()是一个右值,但是operator<<将左值作为第一个参数

有一个通用插入器来获取rvalue流,但它返回一个basic_ostream<charT, traits>&:

template <class charT, class traits, class T>
  basic_ostream<charT, traits>&
  operator<<(basic_ostream<charT, traits>&& os, const T& x);
Run Code Online (Sandbox Code Playgroud)

要为您的示例正常工作,它必须返回stream(std::ostringstram)的派生类型.

  1. 我写的输出操作符确实按预期工作(并满足我给出的所有要求)?或者它可能以意想不到的方式失败或产生其他意外后果?特别是:我std::forward在这里使用正确吗?

你的代码对我来说是正确的.

  1. 有没有更简单的方法来满足要求?

您的代码看起来类似于我为解决此问题而编写的代码(见下文).

  1. 假设这确实是正确的并且是最简单的方法:你认为这样做是个好主意,还是你会建议它(特别是对于我的例子中的流输出,以及作为传递对象的更一般方案)通过功能)?

这个成语对我来说很好看.

libc ++中,我定义了rvalue ostream插入器,如下所示(作为扩展).我试图让它标准化,但是已经很晚了,委员会可以理解为没有更多的颠簸情绪:

template <class _Stream, class _Tp>
inline
typename enable_if
<
    !is_lvalue_reference<_Stream>::value &&
    is_base_of<ios_base, _Stream>::value,
    _Stream&&
>::type
operator<<(_Stream&& __os, const _Tp& __x)
{
    __os << __x;
    return std::move(__os);
}
Run Code Online (Sandbox Code Playgroud)

与您的不同,这只接受右值流.但是和你的一样,它返回具体派生类型,因此适用于你的例子.

  • 感谢您的回答.顺便说一句,我认为你的两个决定在你的库中存在一个缺陷:由于你的rvalue版本的`operator <<`返回一个左值,链接的`operator <<`调用将使用左值版本.由于`operator <<的左值版本不保留确切的类型,我认为你的库将允许`(std :: ostringstream()<< foo).str()`但是*not*`(std :: ostringstream()<< foo << bar).str()`.我认为这是一个相当奇怪的不一致. (4认同)