最近,我经常使用我在 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::pair
,std::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_something
以construct_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_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) ) )
is of type boost::fusion::vector2<double, std::reference_wrapper<double const> >
. 惊喜!
本节旨在说明当前的 STL 行为依赖于协议(例如,使用什么类来标记引用),而另一个(我称之为“自然”行为)使用std::move
(或更准确地说是右值转换)不依赖于协议一个协议,但它更原生于(当前)语言。