简体   繁体   English

ccc错误“试图在具有右值的元组中构造参考元素”,gcc未给出

[英]Clang error “attempted to construct a reference element in a tuple with an rvalue” not given by gcc

I've written the following (relatively) simple implementation of a std::tuple zip function (analogous to Python's zip ) with perfect forwarding: 我编写了以下(相对)简单实现的std::tuple zip函数(类似于Python的zip ),具有完美的转发功能:

template <size_t I, size_t N>
struct tuple_zip_helper {
  template <typename... Tuples>
  constexpr auto operator()(Tuples&&... tuples) const {
    return tuple_cat(
      make_tuple( forward_as_tuple(get<I>(forward<Tuples>(tuples))...) ),
      tuple_zip_helper<I+1, N>()(forward<Tuples>(tuples)...)
    );
  }
};

template <size_t N>
struct tuple_zip_helper<N, N> {
  template <typename... Tuples>
  constexpr auto operator()(Tuples&&...) const {
    return forward_as_tuple();
  }
};

namespace std {
  // Extend min to handle single argument case, for generality
  template <typename T>
  constexpr decltype(auto) min(T&& val) { return forward<T>(val); }
}

template <typename... Tuples>
auto tuple_zip(Tuples&&... tuples) {
  static constexpr size_t min_size = min(tuple_size<decay_t<Tuples>>::value...);
  return tuple_zip_helper<0, min_size>()( forward<Tuples>(tuples)... );
}

This appears to work fine for two or more tuples, even when mixing lvalues and rvalues and even when I use a BlabberMouth class to check for spurious copies and moves: 即使将左值和右值混合,甚至当我使用BlabberMouth类检查伪造的副本和移动时,这对于两个或多个元组似乎也可以正常工作:

template <typename Tuple>
void f(Tuple&& tup) {
  cout << get<0>(get<0>(tup)).data << endl;
}

struct Blabbermouth {
  Blabbermouth(string const& str) : data(str) { }
  Blabbermouth(Blabbermouth const& other) : data(other.data) { cout << data << " copied" << endl; }
  Blabbermouth(Blabbermouth&& other) : data(move(other.data)) { cout << data << " moved" << endl; }
  string data;
};

int main(int argc, char** argv) {
  Blabbermouth x("hello ");
  // prints "hello"
  f(tuple_zip(
      forward_as_tuple(x, 2),
      forward_as_tuple(Blabbermouth("world"), 3)
  ));
}

It also works fine when I give it just one tuple without mixing lvalues and rvalues (clang-3.9, earlier versions of clang choke on this as well): 当我只给它一个tuple而不混合lvalues和rvalues时,它也可以正常工作(clang-3.9,clang choke的早期版本也是如此):

f(tuple_zip( forward_as_tuple(Blabbermouth("world"), 3) ));  // prints "world"

However, when I mix lvalues and rvalues and only give one tuple, clang freaks out about something in a noexecpt specification (but gcc is fine, and even runs correctly): 然而,当我混合左值和右值,并只给一个元组, clang怪胎约在东西noexecpt规范(GCC却是罚款,甚至正常运行):

auto x = BlabberMouth("hello");
f(tuple_zip( forward_as_tuple(x, 3) ));  // clang freaks out, gcc okay

Live Demo 现场演示

What (if anything) am I doing wrong? 我做错了什么(如果有的话)? Should gcc be complaining, or should clang not be complaining? gcc应该抱怨吗,还是clang不要抱怨? Does my code have any dangling references that I'm just "getting lucky" with, and that's why clang is objecting? 我的代码中是否有任何悬而未决的引用,只是我很幸运,所以这就是clang反对的原因吗? Should I have done this differently? 我应该这样做吗? If clang is the one wrong here, can anyone suggest a workaround? 如果clang是这里的错误,那么有人可以建议解决方法吗? (And/or link me to a bug report?) (和/或将我链接到错误报告?)


Update 更新

@Oktalist contributed a much more minimal example that illustrates the same problem: @Oktalist提供了一个更简单的示例来说明同一问题:

struct foo {};

int main(int argc, char** argv)
{
  foo f;
  std::tuple<foo&> t(f);
  std::tuple_cat(std::make_tuple(t), std::make_tuple());
}

(I had considered making my example more minimal as well, but I wasn't sure if what I was doing was exactly analogous to this, mainly because I don't fully understand how perfect forwarding interacts with auto / decltype(auto) return values, return value optimization (RVO), std::get and std::make_tuple , so I wanted to be sure I wasn't doing something else stupid.) (我曾考虑将示例也做得尽可能少,但是我不确定我所做的事情是否与此类似,主要是因为我不完全了解完美转发如何与auto / decltype(auto)返回值交互作用,返回值优化(RVO), std::getstd::make_tuple ,因此我想确保自己没有做其他愚蠢的事情。)

The bug is caused by the calls to tuple_cat (though not exactly in it; it seems to be some messiness inside libc++'s tuple 's byzantine maze of constructors and SFINAE conditions), so the workaround is to avoid using it. 该错误是由对tuple_cat的调用引起的(尽管不完全是对它的调用;它似乎在libc ++的tuple的构造函数和SFINAE条件的拜占庭迷宫中有些混乱),因此解决方法是避免使用它。

There is no point in doing recursive tuple_cat s anyway; 无论如何,执行递归tuple_cat毫无意义; one pack expansion (or two) will do. 一包扩展(或两个)即可。

template<size_t I, typename... Tuples>
constexpr auto tuple_zip_one(Tuples&&... tuples) {
    return forward_as_tuple(get<I>(forward<Tuples>(tuples))...);
}

template<size_t...Is, typename... Tuples>
constexpr auto tuple_zip_helper(index_sequence<Is...>, Tuples&&... tuples) {
    return make_tuple(tuple_zip_one<Is>(forward<Tuples>(tuples)...)...);
}

template <typename... Tuples>
auto tuple_zip(Tuples&&... tuples) {
  static constexpr size_t min_size = min({tuple_size<decay_t<Tuples>>::value...});
  return tuple_zip_helper( make_index_sequence<min_size>(), forward<Tuples>(tuples)... );
}

I took the liberty of removing your UB-inducing min overload and simply using the standard initializer_list version instead. 我随意删除了导致UB的min重载,而只是使用标准的initializer_list版本。

Demo . 演示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM