如何连接两个基于元组的对象

How to concatenate two object which are based on tuple

提问人:newbie 提问时间:7/5/2023 更新时间:7/6/2023 访问量:104

问:

我有一个类模板,它基于一个并且仅由该元组组成。 如何连接此类模板的两个对象,以便将两个类的所有元组成员作为同一类模板的对象。 使用不起作用,可能是因为类型不是元组std::tupletuple_cat

template<typename...Ts>
class foo : private std::tuple<Ts...> {
public:
    foo(Ts...vs) : std::tuple<Ts...>(vs...){}
    //  .
    //  .
    //  .
};



template<typename...T1s, typename...T2s>
foo<T1s..., T2s...> operator+(const foo<T1s...>& foo1, const foo<T2s...>& foo2){
    //  return std::tuple_cat(foo1, foo2); // this does not work
    //  what should be here?
}

foo<int, float, const char*> obj1(1, 1.23, "string1");
foo<const char*, float, int> obj2("string2" , 1.23, 1);

foo<int, float, const char*, const char*, float, int> obj3 = obj1 + obj2;// I want to do this by overloading of operator +
C++ 元组 运 算符重载 variadic 模板

评论

0赞 Sam Varshavchik 7/5/2023
第 1 步:添加类成员。第 2 步:调用 ,向它传递一对 s,并将你的返回类型定义为 。第 3 步:任务完成。const std::tuple<Ts...> &me() const { return *this; }tuple_catme()operator+auto
1赞 NathanOliver 7/5/2023
而不是 derived from ,而是给一个 priave 成员对象。然后可以是 .这还需要您创建一个构造函数,该构造函数允许您将 A 传递给直接初始化元组成员。foostd::tuplefoostd::tupleoperator +return std::tuple_cat(foo1.tuple_member, foo2.tuple_member);std::tuplefoo
0赞 AKL 7/5/2023
@SamVarshavchik不能仅从基地建造!他还应该使用 .请看我的回答。fooStd::apply

答:

1赞 AKL 7/5/2023 #1

您仍然可以通过使用 来实现此功能,但您还需要使用 。std::tuple_catstd::apply

因为 的底座无法从外部访问,所以你有两种选择:tuplefoofoo

正如 Nelfeal 在第一种方法中提到的,您可以声明 作为类模板的好友。operator+foo

第二种方法要求您将任何其他类模板声明为友元类模板,这可能会使其他可能的方法更容易,这些方法涉及另一个 .并具有 as class 方法。foofoooperator+

template<typename...Ts>
class foo : private std::tuple<Ts...> {
    template<typename ... T2s>
    friend class foo;
public:
    foo(Ts...vs) : std::tuple<Ts...>(vs...){}
    
template<typename ... T2s>
    foo<Ts..., T2s...> operator+(const foo<T2s...>& other) const
    {
        const auto& base1 = *static_cast<const std::tuple<Ts ...> *>(this);
        const auto& base2 = static_cast<const std::tuple<T2s ...>&>(other);
        const auto& base = std::tuple_cat(base1, base2);
        auto pass = [](auto&& ... refs){return foo<Ts..., T2s...>(std::move(refs) ...);};
        return std::apply(pass, base);
    }

    //  .
    //  .
    //  .
};

说明

base1和 只是 和 的基础。因为它们是 ,现在您可以使用它们来获得结果的串联基础。 将给定函数(第一个参数)应用于给定(第二个参数)的所有元素。lambda 函数只是根据给定的参数创建一个。base2tuplefoo1foo2tuplestd::tuple_cattuplestd::applytuplepassfoo

现在你可以像这样使用它:

foo<int, float, const char*> obj1(1, 1.23, "string1");
foo<const char*, float, int> obj2("string2" , 1.23, 1);

foo<int, float, const char*, const char*, float, int> obj3 = obj1 + obj2;
// or
auto obj4 = obj1 + obj2;

评论

0赞 Sam Varshavchik 7/5/2023
我相当确定,重载被声明为 a(一个相当常见的错误,即遗漏了问题的关键部分),使所有这些都成为一个有争议的问题。此外,提出的原因制作了大量冗余副本。operator+friend
0赞 AKL 7/6/2023
@SamVarshavchik这两点都达成了一致,但是 OP 可能有一些理由不使用其他构造函数,即使是作为私有构造函数。但是,我改变了我的答案以涵盖您的第一点。请看一下
0赞 Nelfeal 7/6/2023
您仍在制作不必要的副本。
0赞 AKL 7/6/2023
@Nelfeal我知道,但是这个版本的类模板尽可能接近原始版本。我认为您应该在您的版本中制作额外的构造函数。因为如果 OP 想要从元组构造,他只需将其作为默认构造函数或附加构造函数即可。在某些情况下,将其作为私有构造函数可能会产生不可访问错误!例如,在您的版本中,不能从单个对象构造。然后编译器会发出错误privatefooexplicitfoofoostd::tuple
0赞 Nelfeal 7/6/2023
@AKL 这与附加构造函数无关。 创建一个新对象,并复制 中的元素。然后,引用该临时对象。std::tuple<Ts ...>(*this)tuple*this
1赞 Nelfeal 7/5/2023 #2

因为你是私下继承的,所以 无法访问基数,所以你需要以一种或另一种方式修复它。我认为最简单的方法是交一个朋友。然后,通过添加 中的(私有)构造函数,您可以只在正确的类型上使用(通过引用或):std::tupleoperator+operator+foostd::tuplestd::tuple_catstatic_cast

template<typename... Ts>
class foo : private std::tuple<Ts...> {
    explicit foo(std::tuple<Ts...>&& tuple) : std::tuple<Ts...>(std::move(tuple)) {}

public:
    foo(Ts...vs) : std::tuple<Ts...>(vs...){}

    template<typename... T1s, typename... T2s>
    friend foo<T1s..., T2s...> operator+(foo<T1s...> const& foo1, foo<T2s...> const& foo2);
};

template<typename... T1s, typename... T2s>
foo<T1s..., T2s...> operator+(foo<T1s...> const& foo1, foo<T2s...> const& foo2) {
    std::tuple<T1s...> const& tuple1 = foo1;
    std::tuple<T2s...> const& tuple2 = foo2;
    return foo(std::tuple_cat(tuple1, tuple2));
}

演示

如果不需要其他构造函数,可以使用将所有元素传递给主构造函数:std::apply

template<typename... T1s, typename... T2s>
foo<T1s..., T2s...> operator+(foo<T1s...> const& foo1, foo<T2s...> const& foo2) {
    std::tuple<T1s...> const& tuple1 = foo1;
    std::tuple<T2s...> const& tuple2 = foo2;
    auto make_foo = [](auto&&... arg) { return foo(std::move(arg)...); };
    return std::apply(make_foo, std::tuple_cat(tuple1, tuple2));
}

评论

0赞 AKL 7/6/2023
好吧,对不起,看来我错了。但是,这仍然会产生错误,因为它是私有的std::tuple<int> tp{12}; foo foo1{std::move(tp)};
0赞 Nelfeal 7/6/2023
@AKL 是的,它是私有的,因为 OP 可能不想让用户访问转换。我看不出问题。
0赞 AKL 7/6/2023
只要考虑一下这个构造是可以的,而构造给出了我所说的错误。也许OP也想有类型,可能。拥有私有构造函数使它变得不可能!auto f1(){return 12;} auto f2(){return std::tuple{12, 13};} foo foo1 = f1(); foo foo2 = f2(); foo1foo2foo<std::tuple<int, int>>
0赞 AKL 7/6/2023
好吧,不是不可能,但比其他情况更难
0赞 Nelfeal 7/6/2023
公平地说,除非构造函数被标记,否则您必须指定模板参数。我编辑了答案。explicit