The inner factory problem

I apologize in advance as this is a rather complex example that I have simplified as much as I can.
http://coliru.stacked-crooked.com/a/f81521f4a4bd6d0d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
#include <memory>

struct Base
{
    virtual ~Base() = default;

    struct Inner
    {
        Inner(Base &b)
        : inst(b)
        {
        }
        virtual ~Inner() = default;

        virtual void f() = 0;

        virtual Base &instance()
        {
            return inst;
        }

    private:
        Base &inst;
    };

    virtual std::unique_ptr<Inner> new_inner() = 0;
};

struct Derived
: virtual Base
{
    Derived(int x)
    : v{x}
    {
    }

    struct Inner
    : virtual Base::Inner
    {
        Inner(Derived &d)
        : Base::Inner{d}
        , inst(d)
        {
        }

        virtual void f()
        {
            std::cout << inst.v << std::endl;
        }
        virtual void g(int x) //just a random example
        {
            inst.v = x;
        }

        virtual Derived &instance() //fine
        {
            return inst;
        }

    private:
        Derived &inst;
    };

    virtual std::unique_ptr<Inner> new_inner() override //problem
    {
        return std::unique_ptr<Inner>{new Inner{*this}};
    }

private:
    int v;
};

int main()
{
    Derived d {6};
    auto inner = d.new_inner();
    inner->f();
    inner->g(7);
    inner->f();
}
If I just returned raw pointers, then everything would be ok except that obviously the caller would be responsible for wrapping the returned pointer. I'm trying to find a solution that combines the best of both worlds.

Base and Derived are the real objects that we're interested in, and they are part of a large inheritance tree. The Inner classes are directly related to a Base instance. Each class in the inheritance tree needs its own derived Inner to specialize its parent's Inner. If you are given access to an object via Base, you can create Inners and call f(). If you are given access to an object via a Derived, you can create Inners and call either f() or g(). The creation is handled via the virtual new_inner() member functions, which let you create Inner instances for that object type associated with that particular object. There will be multiple overloads and new overloads introduced as you go down the inheritance tree.

Basically, I'm creating polymorphic inner classes. I do have a very good reason for needing this functionality.

I have a solution that is less than ideal, but acceptable:
https://gist.github.com/LB--/51528feec5a44e865abc/revisions (Diff)
http://coliru.stacked-crooked.com/a/5781095ae89bf2c2
Basically, every class implements a copy-paste non-virtual new_inner() function that just calls produce_new_inner(). This is terrible code duplication as it is literal copy and paste - they don't even need to be edited.

I could also use a non-member template function to reduce the code duplication but then the syntax isn't as nice and I have to deal with how the template function will have access rights to call the private virtual function. Do recall that there will be many overloads of these functions.

Is there some more ideal solution?
Last edited on
closed account (10X9216C)
Basically, I'm creating polymorphic inner classes. I do have a very good reason for needing this functionality.

What is the reason? If you want people to help you brainstorm, i feel having the full picture would help greatly. I don't really see the benefit, especially since you are tying it down to a reference of itself (why don't you have this as derived class, are you trying to avoid that problem you had with MI?).
Last edited on
Rename Inner to Event and you have the full picture. My event processing system calls events up their inheritance chain, so code listening to Base::Event can receive Derived::Event. You listen to the events for the type you want to deal with. EDIT: Also, calling a Base::Event for a Derived would result in slicing.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <memory>
#include <type_traits>

 struct Base
{
    virtual ~Base() = default;

    struct event
    {
        event(Base &b) : inst(b) {}

        virtual ~event() = default;

        virtual void f() = 0;

        virtual Base& instance() { return inst ; }

    private:
        Base &inst;
        
        ///////////////////////////////  added /////////////////////////////////////////////
        template < typename T > std::unique_ptr< typename T::event > actual() 
        { return std::unique_ptr< typename T::event >{ dynamic_cast< typename T::event* >(this) } ; }

        template < typename T > friend std::unique_ptr<typename T::event> get_event( T& object )
        {
            static_assert( std::is_base_of<Base,T>::value, "not a derived or same type" ) ;
            auto p = static_cast<Base&>(object).new_inner() ;
            return p ? p->template actual<T>() : std::unique_ptr<typename T::event> { nullptr } ;
        }
        ///////////////////////////////////////////////////////////////////////////////////
    };

    template < typename T > friend std::unique_ptr<typename T::event> get_event( T& object ) ; // *** added ***

    private: virtual event* new_inner() = 0; // ***  we can return a raw pointer; this is private *** 
};

struct Derived : virtual Base
{
    Derived(int x) : v{x} {}

    struct event : virtual Base::event
    {
        using Base::event::event ; // *** why carry excess baggage when we can override instance()? ***

        virtual void f() { std::cout << "Derived::inner::f " << instance().v << '\n' ; }

        virtual void g(int x) { std::cout << "Derived::inner::g\n" ; instance().v = x ; } 

        virtual Derived& instance() { return dynamic_cast<Derived&>( Base::event::instance() ) ; } 
    };

    private: virtual Derived::event* new_inner() override { return new event{*this} ; } // *** private ***

    private: int v;
};

int main()
{
    Derived d {6};
    auto inner = get_event(d);
    inner->f();
    inner->g(7);
    inner->f();
}

http://coliru.stacked-crooked.com/a/b1c43df00918b4e1
Ah, I considered different parts of that individually but never thought I could put them together. That's a really nice solution too as it enforces using the same names for the derived classes. I really appreciate it!

Line 35 was a little confusing to me until I realized that line 26 used friend and not static - they refer to the same function in the same namespace as Base, right? If I have multiple trees with different Base classes, will ADL (if used at all) work or will I need to use different names or have a single common definition for get_event used by all? Separately, ADL should work if the derived classes are in different namespaces, right?
Last edited on
> If I have multiple trees with different Base classes, will ADL (if used at all) work.

Yes. As long as the type involved is not a class that is multiply (publicly) derived from both.

We could also move the functions to namespace scope and rely on SFINAE.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <type_traits>

struct A { friend void foo( A ) { std::cout << "foo in scope of A\n" ; } } ;
struct B { friend void foo( B ) { std::cout << "foo in scope of B\n" ; } } ;

template < typename T > typename std::enable_if< std::is_base_of< A, T >::value >::type bar( T& )
{ std::cout << "bar for types derived from A\n" ; }

template < typename T > typename std::enable_if< std::is_base_of< B, T >::value >::type bar( T& )
{ std::cout << "bar for types derived from B\n" ; }

int main()
{
    struct C : A {} c ; foo(c) ; bar(c) ;
    struct D : B {} d ; foo(d) ; bar(d) ;

    struct AB : A, B {} ab ;
    // foo(ab) ; // *** error: ambiguous
    // bar(ab) ; // *** error: ambiguous
    foo( (B&)ab ) ; // fine
    bar( (A&)ab ) ; // fine
}

http://coliru.stacked-crooked.com/a/beb64300b19c4ff8
Thanks, much appreciated! It's better than what I was anticipating. Maybe in some far future version of C++, there will be a better way to make the original code compile.
Topic archived. No new replies allowed.