完美的传递

时间:2012-01-11 21:31:29

标签: c++ c++11

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

问题如下:

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

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

std::string s = (std::ostringstream() << foo << bar << baz).str();

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

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

  • operator<<会返回ostream&(嗯,至少对于标准的basic_ostream<CharT, Traits>&,其中CharTTraits是从第一个论点)。

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

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

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

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

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

这个额外的规则是你可以从通过函数的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;
}

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

因此我的问题:

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

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

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

1 个答案:

答案 0 :(得分:6)

  

std::ostringstream()是左值,但operator<<取左值   作为第一个论点

有一个通用插件来获取右值流,但它会返回basic_ostream<charT, traits>&

template <class charT, class traits, class T>
  basic_ostream<charT, traits>&
  operator<<(basic_ostream<charT, traits>&& os, const T& x);

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

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

您的代码对我来说是正确的。

  
      
  1. 是否有更简单的方法来满足要求?
  2.   

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

  
      
  1. 假设这确实是正确的并且是最简单的方法:你认为这样做是个好主意,还是你建议反对它   (两者都专门针对我输出的流输出,以及更多   通过函数传递对象的一般方案?
  2.   

这个成语看起来很好。

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

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

与您的不同,此接受右值流。但是和你的一样,它会返回具体的派生类型,因此可以使用你的例子。