std::apply 可能未正确实施

std::apply may not be properly implemented

std::apply 在一些 Whosebug 答案和 n3658, n3915 中被提及,通常定义为:

template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
  return forward<F>(f)(get<I>(forward<Tuple>(t))...);
}

template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
  using Indices = make_index_sequence<tuple_size<decay_t<Tuple>>::value>;
  return apply_impl(forward<F>(f), forward<Tuple>(t), Indices{});
}

但是参考实现 std::apply 函数无法在这种情况下编译(使用 clang 3.8 和 gcc 5.2 测试):

std::apply ([] (int&) {} , std::make_tuple (42));

一种可能的解决方法是简单地从 apply_impl 中删除 std::forward 但保持通用引用不变:

template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
  return forward<F>(f)(get<I>(t)...);
}

此解决方法有什么缺点吗?有没有更方便的解决方案?

UPDATE:另一种可能的解决方法,无需更改 std::apply(受此 SO answer 启发):

template <typename T> constexpr T& make_tmp (T&& t) noexcept { return t; }
...
std::apply ([] (int& i) {} , make_tmp (std::make_tuple (42)));

是否正确且结果定义明确?

当你有一个包含临时对象(或对临时对象的引用)的临时元组时,需要可写引用的函数有权拒绝该应用程序。该函数希望调用者注意它所写的内容,而您正在确定性地丢弃它。

编译代码如下:

int main() {
  int i = 42;
  std::apply ([] (int&) {} , std::tie(i) );
}

make_tuple 创建一个 副本 的元组。 apply 然后在即将被丢弃的副本上调用 passed-in f:这些被完全正确地视为右值。

如果您有一个对象要通过 by-reference 传递给 std::apply,请在元组中放置对它的引用,而不是它的副本。那么 apply 中元组的 rvalue-ness 适用于参考内容。

对于 std::get<N>(some_tuple) 到 return 一个右值,要么:

(A)第n个元素必须是副本,元组必须是右值

(B)第n个元素必须是右值引用,元组必须是右值引用

通过存储左值引用,std::get 永远不会 return 右值引用。

您可能想根据情况调用 std::tiestd::forward_as_tuplestd::make_tuple 适用于当你想在元组中创建副本时,而不是当你存储对其他对象的引用时。

当您 想要放弃任何修改时,有一些方法可以让您将右值作为左值传递,但这些通常是个坏主意,应该在客户端代码中完成到处都是大警告贴纸,而不是隐含在图书馆里。

template<class T>
T& as_lvalue( T&& t ) { return t; }

很简单。