包装对象的语义:默认情况下通过std :: move / std :: ref引用/值

时间:2013-11-19 19:36:13

标签: c++ c++11 stl boost-fusion reference-wrapper

最近我经常使用一种我在C ++ 11中“发现”的自然习语,即包装对象可以在可能的情况下自动保持引用。这里的主要问题是将这个“成语”的行为与标准中的其他行为进行比较(见下文)。

例如:

template<class T>
struct wrap{
    T t;
};
template<class T> wrap<T> make_wrap(T&& t){
    return wrap{std::forward<T>(t)};
}

以这种方式代码

double a = 3.14
double const c = 3.14

我明白了,

typeid( make_wrap(3.14) ) --> wrap<double>
typeid( make_wrap(a) ) --> wrap<double&>
typeid( make_wrap(c) ) --> wrap<double const&>

如果我小心(悬挂引用),我可以很好地处理。如果我想避免引用,我会这样做:

typeid( make_wrap(std::move(a)) ) --> wrap<double> // noref
typeid( make_wrap(std::move(c)) ) --> wrap<double const> // noref

所以,这种行为在C ++ 11中似乎很自然。

然后我又回到了std::pairstd::make_pair,不知怎的,我预料到他们会使用这种新的看似自然的行为,但显然行为是“更传统的”。例如:

typeid( std::make_pair(3.14, 3.14) ) --> std::pair<double, double>
typeid( std::make_pair(a, a) ) --> std::pair<double, double> // noref
typeid( std::make_pair(c, c) ) --> std::pair<double, double> // noref

引用:

typeid( std::make_pair(std::ref(a), std::ref(a) ) ) --> std::pair<double&, double&> // ref
typeid( std::make_pair(std::ref(c), std::ref(c) ) ) --> std::pair<double const&, double const&> // const ref

此处记录了这些内容:http://en.cppreference.com/w/cpp/utility/pair/make_pair

如您所见,这两种行为是“相反的”,在某种意义上,std::refstd::move的补充。因此,这两种行为最终都具有同样的灵活性,但在我看来,std::make_pair行为更难以实现和维护。

问题是: std::make_pair默认情况下丢弃引用的当前行为是向后兼容性问题吗?因为某些历史预期?或者有更深层次的原因仍然存在于C ++ 11中?

实际上,这种std::make_pair行为看起来要难得多,因为它需要std::refstd::reference_wrapper)和std::decay的专业化,甚至看起来不自然(在“C ++ 11移动”的存在下)。同时即使我决定继续使用第一种行为,我也很害怕 即使在C ++ 11中,对于当前的标准,行为也会出乎意料。

事实上,我非常喜欢第一种行为,以至于优雅的解决方案可能会为make_something之类的内容更改前缀construct_something,以便标记行为的差异。 (编辑:其中一条评论建议查看std::forward_as_tuple,因此另一个名称约定可能是forward_as_something)。关于命名,当按值传递时,情况并不明确,pass-by-ref在对象的构造中混合。


EDIT2:这是一个编辑,只是为了回答@Yakk关于能够“复制”具有不同ref / value属性的wrap对象。这不是问题的一部分,它只是实验性代码:

template<class T>
struct wrap{
    T t;
    // "generalized constructor"? // I could be overlooking something important here
    template<class T1 = T> wrap(wrap<T1> const& w) : t(std::move(w.t)){}
    wrap(T&& t_) : t(std::move(t)){} // unfortunately I now have to define an explicit constructor
};

这似乎允许我在不相关的类型wrap<T&>wrap<T>之间进行复制:

auto mw = make_wrap(a);
wrap<double const&> copy0 =mw;
wrap<double&> copy1 = mw; //would be an error if `a` is `const double`, ok
wrap<double> copy2 = mw;

EDIT3:此编辑是添加一个具体示例,其中传统参考演绎可能会失败,具体取决于“协议”。该示例基于Boost.Fusion的使用。

我发现从引用到值的隐式转换可以取决于约定。例如,好的旧Boost.Fusion遵循

的STL约定
  

Fusion的生成功能(例如make_list)默认存储   元素类型为普通非引用类型。

然而,依赖于标记引用的确切“类型”,在Fusion的情况下是boost::ref,而在make_pair的情况下是... std::ref,完全无关的课程。所以,目前,给定

double a;

boost::fusion::make_vector(5., a )的类型为boost::fusion::vector2<double, double>。好的,很好。

boost::fusion::make_vector(5., boost::ref(a) ) ) is boost :: fusion :: vector2`的类型。好的,记录在案。

然而,令人惊讶的是,由于Boost.Fusion不是用C ++ 11 STL编写的,我们得到: boost::fusion::make_vector(5., std::ref(a) ) )的类型为boost::fusion::vector2<double, std::reference_wrapper<double const> >。惊喜!

这部分是为了表明当前的STL行为依赖于协议(例如,用于标记引用的类),而另一个(我称之为“自然”行为)使用std::move(或更确切地说) rvalue casting)不依赖于协议,但它更像是(当前)语言的原生。

0 个答案:

没有答案