将一组转发引用包装在一个元组中

Wrap a pack of forwarding references in a tuple

我有这样的功能

template <typename... Args> void foo(Args&&... args);

我需要在末尾添加一个带有默认参数的额外参数。由于包需要放在最后,我正在考虑将功能更改为

template <typename... Args> void foo(std::tuple<Args&&...> args,
                                     const std::string& name = {});

问题是,在 tuple 中传递参数的最佳方式是什么。 我的理解是 std::tuple<Args&&...> 中的 Args 不再是转发引用,而是严格的右值引用。如何获得 args 的转发引用行为包裹在 tuple 中,例如接受 std::forward_as_tuple 并保留各个元组元素的引用类型。另外,在这里传递元组的最佳方式是什么,

std::tuple<Args&&...> args

const std::tuple<Args&&...>& args

std::tuple<Args&&...>&& args

?

我是否需要在函数内的元组元素上使用 std::forward,或者只是 std::get 它们?

My understanding is that in the std::tuple<Args&&...> the Args are not forwarding references anymore

正确。

but strictly rvalue references

是的,除非明确指定 Args,在这种情况下 reference collapsing 可以将它们变成左值引用,即 foo<int&>(...) 将导致 Args&& -> int& && -> int&

what is the best way to pass the arguments in a tuple.

这取决于 foo 的预期用途。如果你不需要知道 Args... 到底是什么,你可能会逃脱:

template <typename Tuple>
void foo(Tuple&& args, const std::string& name = {});

在这种情况下,仍然可以使用 std::tuple_element_t<N, std::decay_t<Tuple>>.

访问个别类型

如果你想知道foo里面的Args...(没有任何额外的抽象层次),你可能想推断出确切的类型,没有任何参考意义:

template <typename.... Args>
void foo(std::tuple<Args...>&& args, const std::string& name = {});

请注意,如果有人在 std::forward_as_tuple 中使用左值和右值,则值类别将存储在 Args 中,您仍然可以使用 std::forward 转发这些参数(std::forward 仅限于转发引用,将其视为条件转换)。

Also, what's the best way to pass the tuple here

可能 Tuple&& 如前所述。如果没有,那么这又取决于用法。如果您使用 const std::tuple<Args...>&,那么通过查看 std::get 的重载列表,您将看到值类别和常量传播到 return std::get 的值(取模 reference collapsing)。 std::tuple<Args...>&& 也是如此。此外,使用后者,您将不得不使用元组右值作为参数(foo(std::forward_as_tuple(...), ...) 而不是 foo(my_tuple, ...))。

另一种解决方案是接受参数包,并检测最后一个参数是否可以被 const std::string& 绑定:

#include <string>
#include <utility>
#include <tuple>
#include <type_traits>

struct dummy {};

template <typename... Args>
void foo_impl(Args&&... args)
{
    const std::string& s = std::get<sizeof...(Args) - 1>(std::forward_as_tuple(std::forward<Args>(args)...));
}

template <typename... Args>
auto foo(Args&&... args)
    -> std::enable_if_t<std::is_constructible<std::string, std::tuple_element_t<sizeof...(Args), std::tuple<dummy, Args...>>>{}>
{
    foo_impl(std::forward<Args>(args)...);
}

template <typename... Args>
auto foo(Args&&... args)
    -> std::enable_if_t<!std::is_constructible<std::string, std::tuple_element_t<sizeof...(Args), std::tuple<dummy, Args...>>>{}>
{
    foo_impl(std::forward<Args>(args)..., "default");
}

DEMO