如何从参数包构造 std::tuple 个引用?

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

我有一个构建器 class,我想将参数存储为参考以供后续构建使用。

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

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

我发现 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};
}

语法错误为:

error: no matching function for call to 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{}... 是一组相同类型的纯右值。

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 个都将使您的示例代码编译。