atomgalaxy/libciabatta

Protected Member Functions

Closed this issue · 4 comments

First of all, thank you for your library.

I have been playing around with mixins using CRTP techniques and have run into the problem where I can not reliably have a particular class in the mixin call a protected member function of another class in the same mixin. Sometime this works if the order of the mixing is performed just right, but other orderings fail. I have a simple ordering and dependency example on godbolt below;

Simple Dependency Example on godbolt.org

This specific ordering requirement makes mixins using the standard CRTP approach very brittle. It can easily break down if there are circular dependencies;

Circular Dependency Example on godbolt.org

Sometimes adding friend Base; to the class with protected members can help. However, particular orderings which I call twice removed can not be fixed with this particular friend statement. Others have noted this issue in the past and recommend against using CRTP in deep inheritance heirarchies.

Twice Removed Dependency Example on godbolt.org

Do you have any strategies to deal with the general mixin that may contain a large collection of component classes, some of which may depend on internal methods of other component classes, which should not be published in the public interface of the composite mixin class? Is this a fundamental limitation of CRTP based mixins?

While I appreciate the general ease of composing mixins using libciabatta, this issue may limit its adoption.

I think I can see a workaround - though yeah, there is a fundamental problem with flat namespaces like method namespaces.

I encourage you to look at tag_invoke (and my talk on it) to perhaps see how you can use "namespaced" customization point functions. https://www.youtube.com/watch?v=T_bijOA1jts

I'd also encourage you to keep all your mixin stuff either private or public, and then, if you need to specially control the public interface of your final widget, "using" those into the public part of the class (though I forget exactly if that work - I never use anything other than public and private, and even then I mostly use the python '_' prefix conventions for pseudo-private (but actually public) members.

Also, sorry for the long wait - I must have missed the email.

How about something like this? https://godbolt.org/z/e1a6e1

The problem I believe lies in you trying to use "protected" to mean mixin-wide, and that just doesn't work. Instead, if you want to expose a specific interface, look in the godbolt above, or here:

I dropped the "private" and added the PublicInterface wrapper around your mixin compositions, which inherits privately.

This is the "bag" for the sandwich with just the named holes showing :)

template< typename Base > struct A : public Base {
  CIABATTA_DEFAULT_MIXIN_CTOR(A, Base);
  void a_func() {printf("A\n");}
  void internal_a_func() { printf("internal A function called\n"); }
};

template<typename Base>
struct B : public Base {
  CIABATTA_DEFAULT_MIXIN_CTOR(B, Base);
  void b_func() {this->self().internal_a_func();}
};

template<typename Base>
struct C : public Base {
  CIABATTA_DEFAULT_MIXIN_CTOR(C, Base);
  void c_func() {this->self().internal_a_func();}
};

template<typename Base>
struct D : public Base {
  CIABATTA_DEFAULT_MIXIN_CTOR(D, Base);
  void d_func() {this->self().internal_a_func();}
};

struct MyMixin : ciabatta::mixin<MyMixin, D, C, B, A> {
  MyMixin() : mixin() {}
};
struct MyMixinRev : ciabatta::mixin<MyMixinRev, A, B, C, D> {
  MyMixinRev() : mixin() {}
};

template <typename Impl>
struct PublicInterface : private Impl {
    using Impl::a_func;
    using Impl::b_func;
    using Impl::c_func;
    using Impl::d_func;
};

using PublicMyMixin = PublicInterface<MyMixin>;
using PublicMyMixinRev = PublicInterface<MyMixinRev>;


int main() {
  
  PublicMyMixin foo{};
  foo.a_func();
  foo.b_func();
  foo.c_func();
  foo.d_func();

  PublicMyMixinRev bar{};
  bar.a_func();
  /* fails because internal_a_func is protected and inaccessable */
  /* can be fixed if friend Base is added to struct A */
  bar.b_func();
  /* fails because internal_a_func is protected and inaccessable */
  /* Adding friend Base to struct A does not fix this problem */
  /* This looks like the twice removed limitation in CRTP */
  bar.c_func();
  bar.d_func();
  
  return 0;
}

Thanks for the examples and ideas. I am going to close this issue.