简体   繁体   English

在构造过程中使用 std::tuple 成员引用?

[英]Use std::tuple member references during construction?

#include "MassivePOD.h"
#include "DependantClass.h" // Constructor: `DependantClass(MassivePOD&)`

class ExplicitSolution
{
public:
    ExplicitSolution() : m_pod{...}, m_dep{m_pod} { };
private:
    MassivePOD  m_pod;
    DependantClass  m_dep;
};

template<typename... T>
class TemplatedSolution
{
public:
    template<typename... TupArgs> TemplatedSolution(TupArgs...);
private:
    // Can assume in-order construction works
    std::tuple<T...>  m_tuple;
};


int main()
{
    ExplicitSolution a{}; // Easy!
    TemplatedSolution<MassivePOD, DependantClass> b{
        std::forward_as_tuple(...) // Forwarded to MassivePOD constructor
      , std::forward_as_tuple(???) // Forwarded to DependantClass constructor
        }; // Hard?
}

I hope this example illustrates my problem.我希望这个例子能说明我的问题。 I'd like to make a reference to an earlier constructed std::tuple member before the entire std::tuple has been constructed.我想在构造整个std::tuple之前引用一个较早构造的std::tuple成员。 Is there an elegant solution to this?有没有优雅的解决方案? I know it's possible using void * hackery, but I'd rather seek some help before going down that dark lonely road.我知道使用void * hackery 是可能的,但我宁愿在走那条黑暗孤独的道路之前寻求一些帮助。

Idea 1想法1

I've tried making a get_ref function but I get the same problem that I can't access the member function of an object that hasn't been created yet.我试过创建一个get_ref函数,但我遇到了同样的问题,我无法访问尚未创建的对象的成员函数。 Here's one of my attempts anyway, though.不过,这是我的尝试之一。

#include <tuple>
#include <utility>

class Foo // simplified base-case
{
public:
    constexpr Foo(int x, char y, int& z) : m_tup{x, y, z*5.0} { };

    constexpr int& get_int() { return std::get<0>(m_tup); };
    constexpr char& get_char() { return std::get<1>(m_tup); };
    constexpr double& get_double() { return std::get<2>(m_tup); };

private:
    std::tuple<int, char, double>  m_tup;
};

int main()
{
    auto super = Foo(5, 'a', ::get_int()); // ???
}

Demo演示

Idea 2想法2

Then I figured maybe I could do something like what std::function does with std::place_holders , so a static object that contains a pointer/reference to where each element of the std::tuple would be.然后我想也许我可以做一些类似于std::functionstd::place_holders ,所以一个静态对象包含一个指向std::tuple每个元素所在位置的指针/引用。 I think this is worth trying but I have no clue how to implement it in practice...我认为这值得一试,但我不知道如何在实践中实现它......

Another proposal: the tuple class, rather than taking parameters for the constructors of the members, instead takes functions which receive the being-constructed instance and which return the members.另一个建议:元组类,而不是为成员的构造函数接受参数,而是接受接收正在构造的实例并返回成员的函数。 These functions can then refer to the previously-constructed members via getters.这些函数然后可以通过 getter 引用先前构造的成员。 All you have to do is make sure that the getters for earlier members work and don't invoke undefined behavior if called while the later members are being constructed.您所要做的就是确保早期成员的 getter 工作并且如果在构造后期成员时调用,则不会调用未定义的行为。

Here's an example hard-coded for two elements to demonstrate the principle.这是一个为两个元素进行硬编码的示例,以演示该原理。 I will leave it to you to get it to work with n elements.我会把它留给你让它与n元素一起工作。 Note that essentially you would be re-implementing a tuple here - I'm not sure whether you can do this with the existing std::tuple .请注意,本质上您将在此处重新实现一个元组 - 我不确定您是否可以使用现有的std::tuple执行此操作。

template<typename A, typename B>
class TemplatedSolution
{
public:
    template<typename AF, typename BF> TemplatedSolution(AF af, BF bf)
        : a(af(*this))
        , b(bf(*this))
    {
    }

    A& get_first() {
        return a;
    }

    B& get_second() {
        return b;
    }

private:
    A a;
    B b;
};

Usage:用法:

typedef TemplatedSolution<MassivePOD, DependantClass> ClassT;

ClassT b{
    [](ClassT& res) { return MassivePOD(); },
    [](ClassT& res) { return DependantClass(res.get_first()); },
};

Because of return-value optimization, the MassivePOD s will get constructed right in the TemplatedSolution instance.由于返回值优化, MassivePOD将在TemplatedSolution实例中正确构造。

A fully-working example which demonstrates no copies is available here .此处提供一个完整的示例,它演示了没有副本。

What about using std::shared_ptr and storing it as a std::shared_ptr in TupleWrapper instead?使用std::shared_ptr并将其存储为TupleWrapperstd::shared_ptr TupleWrapper样?

auto p_first_member_tmp = std::make_shared<MassivePOD>(
    std::forward_as_tuple(...));

TupleWrapper<std::shared_ptr<MassivePOD>, DependantClass> wrapper{
    p_first_member_tmp, 
    std::forward_as_tuple(*p_first_member_tmp, ...)}

It is an extra level of indirection, but your expensive-to-copy-or-move structure won't be copied or moved at least.这是一个额外的间接级别,但至少不会复制或移动昂贵的复制或移动结构。

I was considering why this is never an issue in a language like Python, and it's because the object only gets created once, then everything else just points to it - which is approximated by this approach here.我在考虑为什么这在像 Python 这样的语言中从来都不是问题,这是因为对象只被创建一次,然后其他一切都指向它——这里的这种方法是近似的。 You also won't have to keep any additional track of the lifetime of the object since shared_ptr does that for you already.您也不必对对象的生命周期进行任何额外跟踪,因为shared_ptr已经为您完成了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM