我们可以使用C ++功能解决此问题吗?
C ++ 11增加了成员函数ref-qualifiers,它允许限制可以调用成员函数的类实例(表达式)的值类别。例如:
struct foo {
void bar() & {} // lvalue-ref-qualified
};
foo& lvalue ();
foo prvalue();
lvalue ().bar(); // OK
prvalue().bar(); // error
调用begin
成员函数时,我们知道很可能还需要调用end
成员函数(或类似的东西size
,以获取范围的大小)。这需要我们对左值进行运算,因为我们需要对其进行两次寻址。因此,您可以争辩说这些成员函数应该是左值引用限定的。
但是,这可能无法解决根本问题:别名。的begin
和end
成员函数别名对象,或由对象管理的资源。如果我们更换begin
和end
由一个单一的功能range
,我们应该提供一个可以在右值被称为:
struct foo {
vector<int> arr;
auto range() & // C++14 return type deduction for brevity
{ return std::make_pair(arr.begin(), arr.end()); }
};
for(auto const& e : foo().range()) // error
这可能是有效的用例,但是上面的定义range
不允许使用。由于在成员函数调用之后我们无法解决临时问题,因此返回一个容器(即拥有范围)可能更合理:
struct foo {
vector<int> arr;
auto range() &
{ return std::make_pair(arr.begin(), arr.end()); }
auto range() &&
{ return std::move(arr); }
};
for(auto const& e : foo().range()) // OK
将其应用于OP的案例,并进行少量代码审查
struct B {
A m_a;
A & a() { return m_a; }
};
此成员函数更改表达式的值类别:B()
是prvalue,但是B().a()
是lvalue。另一方面,B().m_a
是一个右值。因此,让我们从保持一致开始。有两种方法可以做到这一点:
struct B {
A m_a;
A & a() & { return m_a; }
A && a() && { return std::move(m_a); }
// or
A a() && { return std::move(m_a); }
};
如上所述,第二个版本将在OP中解决该问题。
此外,我们可以限制B
的成员函数:
struct A {
// [...]
int * begin() & { return &v[0]; }
int * end () & { return &v[3]; }
int v[3];
};
这不会对OP的代码产生任何影响,因为:
基于范围的for循环中位于the 之后的表达式结果将绑定到参考变量。这个变量(作为用于访问其begin
和end
成员函数的表达式)是一个左值。
当然,问题是默认规则是否应为“对rvalues的成员函数进行别名处理应返回拥有所有资源的对象,除非有充分的理由不这样做”。它返回的别名可以合法使用,但是在您遇到它时会很危险:它不能用于延长其“父”临时对象的寿命:
// using the OP's definition of `struct B`,
// or version 1, `A && a() &&;`
A&& a = B().a(); // bug: binds directly, dangling reference
A const& a = B().a(); // bug: same as above
A a = B().a(); // OK
A&& a = B().m_a; // OK: extends the lifetime of the temporary
在C ++ 2a中,我认为您应该按以下方法解决此(或类似问题)问题:
for( B b = bar(); auto i : b.a() )
代替OP的
for( auto i : bar().a() )
解决方法手动指定的生存期b
为for循环的整个块。
引入此初始声明的提案
现场演示