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

时间:2019-09-13 16:54:22

标签: c++ c++17 variadic-templates stdtuple

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

我想将可变数量的参数传递给我的类,使用类模板参数推导来推断模板参数,并将那些传递的参数作为引用存储在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};
}

语法错误是:

  

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

3 个答案:

答案 0 :(得分:5)

如果您只想参考,请直接使用它们:

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&...>进行解析,但后来更简单且提供了解决方案;-)。

答案 1 :(得分:2)

另一种方式:

#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)};                                                                               
}   

答案 2 :(得分:2)

Builder(Ts&... ts)是一组左值引用。

Ts{}...是一包相同类型的prvalue。

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)

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

  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)很有用。

如果您的Builder对象不会超过当前行,并且您甚至不想为移动对象付费,则

(1)很有用。比(1)还危险。

全部3个都将使您的示例代码编译。