Apple LLVM 7.0.0的c ++ 11参数包错误行为,但适用于GCC-5.1

时间:2016-01-16 21:10:02

标签: c++ templates c++11 gcc clang

从这个问题的先前版本开始,感谢@Gene,我现在能够使用更简单的示例重现此行为。

#include <iostream>
#include <vector>

class Wrapper
{
  std::vector<int> const& bc;
public:
  Wrapper(std::vector<int> const& bc) : bc(bc) { }
  int GetSize() const { return bc.size(); }
};

class Adapter
{
  Wrapper wrapper;
public:
  Adapter(Wrapper&& w) : wrapper(w) { }
  int GetSize() const { return wrapper.GetSize(); }
};

template <class T>
class Mixin : public Adapter
{
public:
  //< Replace "Types ... args" with "Types& ... args" and it works even with Apple LLVM
  template <class ... Types>
  Mixin(Types ... args) : Adapter(T(args...)) { }
};

int main()
{
  std::vector<int> data;
  data.push_back(5);
  data.push_back(42);
  Mixin<std::vector<int>> mixin(data);
  std::cout << "data:  " << data.size() << "\n";
  std::cout << "mixin: " << mixin.GetSize() << "\n";
  return 0;
}

使用Apple LLVM的结果,使用-std=c++11-std=c++14进行了测试:

data:  2
mixin: -597183193

有趣的是,我已经测试了这段代码@ideone,它使用了启用了C ++ 14的gcc-5.1,并且按预期工作!

data:  2
mixin: 2

为什么mixin.GetSize()会在Clang上返回垃圾值?为什么它会与GCC-5.1一起使用?

@Gene建议我使用Types ... args创建向量的临时副本(并使用Types& ... args使其与LLVM一起使用),但该副本将包含相同的元素(因此也有相同的尺寸。)

1 个答案:

答案 0 :(得分:3)

您有一个悬空参考,而mixin.GetSize()正在屈服于undefined behavior

  • Mixin的构造函数内部,T = std::vector<int>,因此Adapter(T(args...))正在为Adapter的构造函数传递临时std::vector<int> < / LI>
  • Adapter的构造函数参数是Wrapper&&,但我们传递的是std::vector<int>&&,因此我们调用Wrapper的隐式转换构造函数
  • Wrapper的构造函数参数是std::vector<int> const&,我们将它传递给std::vector<int>&&; rvalues被允许绑定到const-lvalue引用,所以这语法很好并且编译得很好,但实际上我们将Wrapper::bc绑定到临时
  • 构建完成后,Mixin构造函数中创建的临时生命周期结束,Wrapper::bc成为悬空引用;现在调用Adapter::GetSize会产生UB

Mixin的构造函数参数从Types...更改为Types&...时,Adapter(T(args...)) 仍然传递Adapter构造函数临时std::vector<int>;它只有出现才能工作,因为你看到了不同的UB表现形式(由于制作std::vector<int>的副本少了一半,堆栈看起来有点不同)。即,两个版本的代码同样破坏/错误!

所以,具体回答:

  

为什么mixin.GetSize()会在Clang上返回垃圾值?为什么它会与GCC-5.1一起使用?

因为未定义行为未定义。 ; - ]看似工作是一种可能的结果,但代码仍然被打破,正确的外观纯粹是肤浅的。