如何从参数包构造引用的 std::元组?

How can I construct std::tuple of references from a parameter pack?

本文关键字:std 元组 引用 参数 包构造      更新时间:2023-10-16

我有一个构建器类,我想将参数存储为参考,以便在后续构建中使用。

我想将可变数量的参数传递给我的类,使用类模板参数推导推断模板参数,并将这些传递的参数作为引用存储在 std::tuple 中。

从参数包转换为 std::元组引用的最简单方法是什么?

我找到了 std::forward_as_tuple 它的功能与我想要的类似,但我不想要转发引用,而且它在成员元组初始化期间给出了语法错误。

template <typename... Ts>
struct Builder
{
using ArgsT = decltype(std::forward_as_tuple(Ts{}...));
ArgsT args_;
Builder(Ts&... ts) : args_(ts...) {}
};
int main()
{
struct Foo
{
int a;
double b;
};
Foo foo{};
Builder fooBuilder{foo.a, foo.b};
}

语法错误为:

错误:调用std::tuple<int&&, double&&>::tuple(int&, double&)没有匹配函数

如果您只是想要引用,请直接使用它们:

template <typename... Ts>
struct Builder
{
using ArgsT = std::tuple<Ts&...>;
ArgsT args_;
Builder(Ts&... ts) : args_(ts...) {}
};
int main()
{
struct Foo
{
int a;
double b;
};
Foo foo{};
Builder fooBuilder{foo.a, foo.b};
}

对于您的代码:

decltype(std::forward_as_tuple(Ts{}...))std::tuple<Ts&&...>中解决。
Ts{}创建一个临时(并要求你的类型是默认的可构造的(。

而且您不能将int&绑定到int&&.

您可以使用decltype(std::forward_as_tuple(std::declval<Ts&>()...)),它在std::tuple<Ts&...>中解析,但稍后更简单并提供解决方案;-(。

另一种方式:

#include <tuple>
template<typename Tuple>                                                                                                    
struct Builder                                                                                                      
{                                                                                                                            
Tuple args_;                                                                                            
Builder(Tuple const& t) : args_(t) {}                                                                    
};
int main()                                                                                                                   
{                                                                                                                            
struct Foo                                                                                                               
{                                                                                                                        
int a;                                                                                                               
double b;                                                                                                            
};                                                                                                                       
Foo foo{};                                                                                                               
Builder fooBuilder{std::tie(foo.a, foo.b)};                                                                               
}   
Builder(Ts&... ts)

是一包左值引用。

Ts{}...是同一类型的一组 prvalues。

std::forward_as_tuple(Ts{}...)是一个元组,其中包含对同一类型的右值引用。

左值引用和右值引用不是一回事;不能将一个分配给另一个。 因此args_(ts...)会生成相应的错误消息。

有多种方法可以解决您的问题。

template <typename... Ts>
struct Builder
{
using ArgsT = std::tuple<Ts&&...>; // (1)
using ArgsT = std::tuple<Ts&...>;  // (2)
using ArgsT = std::tuple<Ts...>;   // (3)

所有这 3 个实际上是解决问题的合理方法,具体取决于以后的代码选项。 根据您的真正问题选择一个。

ArgsT args_;

这里的内容:

Builder(Ts&&... ts) : args_(std::forward<Ts>(ts)...) {}
Builder(Ts&... ts) : args_(ts...) {}
Builder(Ts&&... ts) : args_(std::forward<Ts>(ts)...) {}

对于上述 3 种情况中的每一种。

在案例 (1( 中,您将参数完美转发到元组中,并且元组存储对任何右值参数的右值引用。 在情况 (2( 中,您只采用左值参数,并存储对它们的左值引用元组。 在案例(3(中,您将参数完美地转发到元组中,如果参数是右值并且左值引用它,则存储值,则值是左值。

(3( 在您希望引用超过当前行的寿命时很有用;唯一安全的方法是存储副本,然后移动到其中。

(2( 如果您只想引用左值,则很有用。

(1( 如果您的 Builder 对象不会超过当前行的寿命,并且您甚至不想为移动对象付费,则很有用。 它比(1(更危险。

所有 3 个都将使您的示例代码编译。