0

最近,我经常使用我在 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::ref是对std::move. 所以这两种行为最终都同样灵活,但在我看来,这种std::make_pair行为更难实现和维护。

问题是:当前std::make_pair默认丢弃引用的行为是否只是向后兼容性问题?因为一些历史预期?还是 C++11 中仍然存在更深层次的原因?

事实上,这种行为看起来std::make_pair更难实现,因为它需要对std::ref( std::reference_wrapper)进行专门std::decay化,甚至看起来不自然(在“C++11 move”的情况下)。同时,即使我决定继续使用第一种行为,我也担心这种行为相对于当前标准来说是非常出乎意料的,即使在 C++11 中也是如此。

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


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

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的情况下是,在is...boost::ref的情况下,是一个完全不相关的类。所以,目前,给定make_pairstd::ref

double a;

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

以及boost::fusion::make_vector(5., boost::ref(a) ) ) isboost::fusion::vector2` 的类型。好的,如文件所述。

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

本节旨在说明当前的 STL 行为依赖于协议(例如,使用什么类来标记引用),而另一个(我称之为“自然”行为)使用std::move(或更准确地说是右值转换)不依赖于协议一个协议,但它更原生于(当前)语言。

4

0 回答 0