C++11 嵌套std::前向作为元组和分段错误

C++11 嵌套std::前向作为元组和分段错误,c++11,segmentation-fault,rvalue-reference,expression-templates,stdtuple,C++11,Segmentation Fault,Rvalue Reference,Expression Templates,Stdtuple,我的实际问题要复杂得多,在这里给出一个简短的具体例子来重现它似乎非常困难。因此,我在这里发布了一个可能相关的不同小示例,其讨论也可能有助于解决实际问题: // A: works fine (prints '2') cout << std::get <0>(std::get <1>( std::forward_as_tuple(3, std::forward_as_tuple(2, 0))) ) << endl; // B: fine in

我的实际问题要复杂得多,在这里给出一个简短的具体例子来重现它似乎非常困难。因此,我在这里发布了一个可能相关的不同小示例,其讨论也可能有助于解决实际问题:

// A: works fine (prints '2')
cout << std::get <0>(std::get <1>(
    std::forward_as_tuple(3, std::forward_as_tuple(2, 0)))
) << endl;

// B: fine in Clang, segmentation fault in GCC with -Os
auto x = std::forward_as_tuple(3, std::forward_as_tuple(2, 0));
cout << std::get <0>(std::get <1>(x)) << endl;
根据这些定义,我得到了完全相同的行为:

// A: works fine (prints '2')
cout << fst(snd(make(3, make(2, 0)))) << endl;

// B: fine in Clang, segmentation fault in GCC with -Os
auto z = make(3, make(2, 0));
cout << fst(snd(z)) << endl;
//A:工作正常(打印“2”)

这里的问题是这样一句话:“如果临时工应该活得和他们被引用的时间一样长。”这只在有限的情况下是正确的,你的程序并不是这些情况中的一个例子。您正在存储一个元组,其中包含对在完整表达式末尾被销毁的临时变量的引用。这个程序非常清楚地演示了它():

node
替换为
node
是一个技巧:由于
A
是一个通用引用,因此
A
的实际类型将是左值参数的左值引用,右值参数的非引用类型。引用折叠规则对这种用法以及完美的转发都有好处

编辑:至于为什么这个场景中的临时对象的生存期没有延长到引用的生存期,我们必须看看C++11 12.2临时对象[class.Temporary]第4段:

有两种情况下,时间单位在完全表达结束时的不同点被销毁。第一个上下文是调用默认构造函数初始化数组的元素时。如果构造函数有一个或多个默认参数,则在构造下一个数组元素(如果有)之前,将按顺序销毁在默认参数中创建的每个临时变量

更为复杂的第5段:

第二个上下文是将引用绑定到临时上下文。引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生存期内持续存在,但以下情况除外:

  • 临时绑定到构造函数的ctor初始值设定项(12.6.2)中的引用成员,直到 构造函数退出

  • 在函数调用(5.2.2)中临时绑定到参考参数的情况持续存在,直到完成 包含调用的完整表达式

  • 函数返回语句(6.6.3)中返回值的临时绑定的生存期不是 扩展的;在return语句的完整表达式末尾销毁临时表达式

  • 临时绑定到新初始值设定项(5.3.4)中的引用将持续,直到包含新初始值设定项的完整表达式完成。[示例:

  • struct S {
      S();
      S(int);
      friend S operator+(const S&, const S&);
      ~S();
    };
    S obj1;
    const S& cr = S(16)+S(23);
    S obj2;
    
表达式
S(16)+S(23)
创建三个临时变量:第一个临时变量
T1
用于保存表达式
S(16)
的结果,第二个临时变量
T2
用于保存表达式S(23)的结果,以及第三个临时
T3
,用于保存这两个表达式相加的结果。然后将临时
T3
绑定到引用
cr
。未指定是首先创建
T1
还是
T2
。在
T2
之前创建
T1
的实现中,这是保证
T2
T1
之前被销毁。临时
T1
T2
绑定到
operator+
的引用参数;这些临时变量在包含对
operator+
的调用的完整表达式末尾被销毁。临时
T3
绑定到引用
 cr
在cr
的生命周期结束时被销毁, 也就是说,在程序结束时。此外,
T3
的销毁顺序考虑了具有静态存储持续时间的其他对象的销毁顺序。也就是说,因为
obj1
T3
之前构造,
T3
obj2
之前构造,所以可以保证
obj2
在T3之前销毁,而
T3
obj1
之前销毁-结束示例]


您正在将临时“绑定到构造函数的ctor初始值设定项中的引用成员”。

非常感谢您的回复。令人惊讶的是,我甚至没有想到这种调试。。。无论如何,我有几个问题,一次一个:(1)int&&x=3怎么样;cout问题(2):我认为我的“实际问题”虽然更复杂,但很可能确实是由于这个问题(尽管程序甚至在单个表达式上崩溃),而您的解决方案似乎是合理的。然而,我关心的不仅仅是左值引用;它还涉及非引用类型,在本例中,这些类型将通过值传递和存储。它们可能很大,并且将在单个表达式中传递/复制/移动多次。所以,除了在自由存储上存储数据并具有移动构造函数的对象之外,我是否只依赖复制省略来避免复制?用生存期扩展的东西扩展了答案。简而言之(1)是的,这是“有限的情况”。它与您的示例的不同之处在于它将临时对象直接绑定到引用,而不是对象的引用成员。(2) 是的,如果通过右值引用传递移动对象的代价会很高。如果您无法避免这种情况,您可以通过返回纯引用并引入另一个“存储”类来更改设计,该类在从引用元组分配时会吞下右值。再次感谢您提供的更多详细信息。标准中的这些异常总是在您不希望它们出现的地方弹出:-)第一次尝试替换
A&&
->
A
似乎是可行的,尽管编译时间再次增加。改变只发生在pro中的几个地方
struct foo {
    int value;
    foo(int v) : value(v) {
        std::cout << "foo(" << value << ")\n" << std::flush;
    }
    ~foo() {
        std::cout << "~foo(" << value << ")\n" << std::flush;
    }
    foo(const foo&) = delete;
    foo& operator = (const foo&) = delete;
    friend std::ostream& operator << (std::ostream& os,
                                      const foo& f) {
        os << f.value;
        return os;
    }
};

template <typename A, typename B>
struct node { A a; B b; };

template <typename... A>
node <A&&...> make(A&&... a)
{
    return node <A&&...>{std::forward <A>(a)...};
}

template <typename N>
auto fst(N&& n)
-> decltype((std::forward <N>(n).a))
    { return std::forward <N>(n).a; }

template <typename N>
auto snd(N&& n)
-> decltype((std::forward <N>(n).b))
    { return std::forward <N>(n).b; }

int main() {
    using namespace std;
    // A: works fine (prints '2')
    cout << fst(snd(make(foo(3), make(foo(2), foo(0))))) << endl;

    // B: fine in Clang, segmentation fault in GCC with -Os
    auto z = make(foo(3), make(foo(2), foo(0)));
    cout << "referencing: " << flush;
    cout << fst(snd(z)) << endl;
}
template <typename... A>
node<A...> make(A&&... a)
{
    return node<A...>{std::forward <A>(a)...};
}
struct S { int mi; const std::pair<int,int>& mp; };
S a { 1, {2,3} };
S* p = new S{ 1, {2,3} }; // Creates dangling reference
struct S {
  S();
  S(int);
  friend S operator+(const S&, const S&);
  ~S();
};
S obj1;
const S& cr = S(16)+S(23);
S obj2;