Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding to defer "child" object construction with `operator<<`

Let's say I have a container object that stores an std::vector of polymorphic children.

struct Child
{
    Child(Parent& mParent) { /* ... */ }
    virtual ~Child() { }
};

class Parent
{
    private:
        std::vector<std::unique_ptr<Child>> children;

        template<typename T, typename... TArgs> 
        auto& mkChild(TArgs&&... mArgs)
        {
            // `static_assert` that `T` is derived from `Child`...
            children.emplace_back(std::make_unique<T>(std::forward<TArgs>(mArgs)...));
            return *children.back();
        }

    public:
        template<typename T, typename... TArgs> 
        auto& add(TArgs&&... mArgs)
        {
            mkChild<T>(std::forward<TArgs>(mArgs)...));
            return *this;
        }
};

Now I can use the Parent class like this:

int main()
{
    Parent p;
    p.add<Child1>(some_args1).add<Child2>(some_args2);
}

While this syntax achieves what I want to do (chaining addition of children to a single parent), I find it very hard to read, especially in my real use case.

I would really like to use operator<< instead. But I cannot figure out a way to construct the children in place.

// Desired syntax
int main()
{
    Parent p;
    p << mk<Child1>(some_args1) << mk<Child2>(some_args2);
}

Notice how I never specify the parent in the mk function.

I do not want to say mk<Child1>(p, some_args1). The compiler should figure out p from the chaining of operator<<.

Is there any way I can implement this mk function generating code equal to the one generated via the .add<T>(...) chaining?

The only way I managed to implement this is using a man-in-the-middle struct that holds the construction variadic parameters for the child class.

template<typename T, typename... TArgs> struct DeferCtor
{
    std::tuple<TArgs...> ctorArgs;
};

Then operator<<(DeferCtor<T, TArgs...>&) would deal with the object's construction inside Parent.

Is there a way to avoid this step, while still having the desired syntax? (Not passing the parent instance in the mk function.)

like image 571
Vittorio Romeo Avatar asked Mar 17 '23 18:03

Vittorio Romeo


2 Answers

You aren't really making objects in place in your existing code -- you're making the child objects on the heap with a unique_ptr and then moving that unique_ptr into parent. You can do the same with your operator<< if you just define it as taking a unique_ptr:

Parent &Parent::operator<<(std::unique_ptr<Child> ch) {
    children.emplace_back(std::move(ch)); }

Now assuming your mk global function is essentially just an alias for make_unique:

template<typename T, typename... TArgs> 
std::unique_ptr<T> mk(TArgs&&... mArgs) {
    return std::make_unique<T>(std::forward<TArgs>(mArgs)...)); }

you should be able to use your desired syntax.

like image 89
Chris Dodd Avatar answered Apr 01 '23 20:04

Chris Dodd


(based on my previous comments). I suggest you construct the unique_ptr of base and feed that to opertor<< (ideone link). No need to get fancy or complicated.

using namespace std;
using mk = make_unique; 

#include <memory>
#include <iostream>

class B {};
class D : public B {};
class E : public B {};

class A {
public:
    A & operator << ( std::unique_ptr<B> bp){
        std::cout << " added a value " << std::endl;
        // children.push_back(move(bp));
        return *this;
    }
};

int main() {
    // your code goes here
    A a;
    a << mk<D>( .. some argument ) <<  mk<E>( other arguments) ;
}
like image 22
Johan Lundberg Avatar answered Apr 01 '23 19:04

Johan Lundberg