5

将右值引用绑定到给定对象或其临时副本的最佳方法是什么?

A &&var_or_dummy = modify? static_cast<A&&>( my_A )
                         : static_cast<A&&>( static_cast<A>( my_A ) );

(这段代码在我最近的 GCC 4.6 上不起作用……我记得它以前可以工作,但现在它总是返回一个副本。)

在第一行,从左值转换为 x 值static_castmy_A(C++0x §5.2.9/1-3)static_cast第二行的内部执行左值到右值的转换,外部的从这个prvalue获得一个xvalue。

这似乎是受支持的,因为根据 §12.2/5,命名引用有条件地绑定到临时。相同的技巧在 C++03 中的工作方式与const引用相同。

我也可以不那么冗长地写同样的东西:

A &&var_or_dummy = modify? std::move( my_A )
                         : static_cast<A&&>( A( my_A ) );

现在它更短了。第一个缩写是有问题的:move应该表示对象正在发生某些事情,而不仅仅是左值到x值到左值的洗牌。令人困惑的是,move不能在 之后使用,:因为函数调用会中断临时引用绑定。语法A(my_A)可能比 更清晰static_cast,但它在技术上等同于 C 风格的演员表。

我也可以一路走下去,完全用 C 风格的演员表来写:

A &&var_or_dummy = modify? (A&&)( my_A ) : (A&&)( A( my_A ) );

毕竟,如果这要成为一个成语,它一定很方便,而且static_cast无论如何也不能真正保护我免受任何伤害——真正的危险是无法直接绑定到my_A此案中true

另一方面,这很容易被重复三遍的类型名所支配。如果A被替换为一个又大又丑的模板 ID,我真的想要一个真正的捷径。

(请注意,V尽管出现五次,但只评估一次:)

#define VAR_OR_DUMMY( C, V ) ( (C)? \
  static_cast< typename std::remove_reference< decltype(V) >::type && >( V ) \
: static_cast< typename std::remove_reference< decltype(V) >::type && >   (  \
  static_cast< typename std::remove_reference< decltype(V) >::type >( V ) ) )

像宏一样骇人听闻,我认为这是最好的选择。这有点危险,因为它返回一个 xvalue,所以它不应该在引用初始化之外使用。

一定有什么我没有想到的……建议?

4

3 回答 3

2

我看到你的方法有两个问题。

你依赖的行为

int   i = 0;
int&  j = true?      i  :      i;
int&& k = true? move(i) : move(i);
assert(&i == &j); // OK, Guaranteed since C++98
assert(&i == &k); // Does this hold as well?

当前标准草案 N3126 包含 5.16/4:

如果[条件运算符]的第二个和第三个操作数是相同值类别的glvalues并且具有相同的类型,则结果是该类型和值类别

这让我认为上述两个断言应该成立。但是使用 GCC 4.5.1,第二个失败了。我相信这是一个 GCC 错误。

此外,您依赖编译器来延长临时对象的生命周期,y参考以下示例:

A func();

A&& x = func();                   // #1
A&& y = static_cast<A&&>(func()); // #2

x不会是一个悬而未决的参考,但我不太确定y. 我认为关于延长临时对象生命周期的规则只适用于初始化表达式是右值的情况。至少,这将大大简化实现。此外,GCC 似乎同意我的观点。在第二种情况下,GCC 不会延长临时 A 对象的生命周期。这将是您方法中的悬空引用问题

更新:根据 12.2/5,临时对象的生命周期在 #1 和 #2 两种情况下都应该延长。例外列表中的所有要点似乎都不适用于此处。同样,GCC 在这方面似乎有问题。

解决您的问题的一种简单方法是:

vector<A> tempcopy;
if (!modify) tempcopy.push_back(myA);
A& ref = modify ? myA : tempcopy.back();

或者,您可以使用 boost::scoped_ptr 而不是向量。

于 2010-10-09T16:18:17.130 回答
2

只需通过额外的函数调用来避免这整个混乱:

void f(bool modify, A &obj) {
  return [&](A &&obj) {
    real();
    work();
  }(modify ? std::move(obj) : std::move(A(obj)));
}

代替:

void f(bool modify, A &obj) {
  A &&var_or_dummy = /* ??? */;
  real();
  work();
}

它是lambdas,lambdas,无处不在

于 2010-10-09T09:41:57.067 回答
0

xvalue 安全性问题可以通过提供在内部表达式中使用的替代方案来解决。问题完全不同,现在我们想要 xvalue 结果并且可以使用函数:

template< typename T >
T &var_or_dummy( bool modify, T &var, T &&dummy = T() ) {
    if ( modify ) return var;
    else return dummy = var;
}

    maybe_get_result( arg, var_or_dummy( want_it, var ) );

现在该类型必须是可默认构造的,并且始终构造虚拟对象。有条件地评估副本。我不认为我真的想处理做太多这样的代码。

Boost Optional会有所帮助;它只需要 CopyConstructible T:

template< typename T >
T &var_or_dummy( bool modify, T &var,
                 boost::optional< T > &&dummy = boost::optional< T >() ) {
    if ( modify ) return var;
    else return dummy = var;
}

Optional 很有用,但它与 C++0x 联合有一些重叠。重新实现并不难。

template< class T >
struct optional_union {
    bool valid;
    union storage {
        T obj; // union of one non-POD member simply reserves storage

        storage() {} // uh, what could the constructor/destructor possibly do??
        ~storage() {}
    } s;

    optional_union() : valid( false ) {}
    optional_union &operator=( T const &in ) {
        new( &s.obj ) T( in ); // precondition: ! valid
        valid = true;
        return *this; 
    }
    ~optional_union()
        { if ( valid ) s.obj.~T(); }
};

template< typename T >
T &var_or_dummy( bool modify, T &var,
                 optional_union< T > &&dummy = optional_union< T >() ) {
    if ( modify ) return var;
    else return ( dummy = var ).s.obj;
}

该类optional_union仅适用于此应用程序......显然它可以扩展很多。

于 2010-10-10T02:07:47.360 回答