c++c++20crtp

Calling potential variadic base class protected member functions


For context: I'm constructing a CRTP Mixin for my derived class.

Now I want to call all protected base class member functions if they exist. I want to have the base class member functions not exposed to the user.

For this I have the following code:

struct A {
   protected:
    void fooImpl() { std::cout << "A::fooImpl"; }
};

struct C {};

template <typename... T>
struct B : T... {
    void foo() {
        auto visitor = [](auto& s) {
            if constexpr (requires { s.fooImpl(); }) 
                s.fooImpl();
        };
        (visitor(static_cast<T&>(*this)), ...);
    }
};

int main() {
    B<A, C> b;
    b.foo();
    return 0;
}

Godbolt This code does not call the fooImpl from C (as expected) and also not the one from A.

I assume this is due to the static_cast, which removes the protected visibility of fooImpl. Is this expected? I assume so when following the discussion from Access to protected constructor of base class, which also applies to my case, doesn't it?

I have two solutions, which I think are not elegant.

  1. Going the CRTP path and forward the derived class to the base to make derived a friend.
template <typename S>
struct A {
    friend S;

   private:
    void fooImpl() { std::cout << "A::fooImpl"; }
};

template <typename S>
struct C {};

template <template <typename> class... T>
struct B : T<B<T...>>... {
    void foo() {
        auto visitor = [](auto& s) {
            if constexpr (requires { s.fooImpl(); }) 
                s.fooImpl();
        };
        (visitor(static_cast<T<B<T...>>&>(*this)), ...);
    }
};

Godbolt This works but I wonder if there is a better way. I also have to refactor all the template signature of my class, which is not so nice.

  1. Hiding the base class foos by calling convention and rename fooImpl to foo
struct A {
   public:
    void foo() { std::cout << "A::foo"; }
};

struct C {};

template <typename... T>
struct B : T... {
    void foo() {
        auto visitor = [](auto& s) {
            if constexpr (requires { s.foo(); }) 
                s.foo();
        };
        (visitor(static_cast<T&>(*this)), ...);
    }
};

Godbolt

This also works but I also think maybe I have overengineered this. Anyway the base class member function are now also hidden to the user. Is there a nicer way to achieve this? Thanks!

EDIT: Refactor of the last question. Is there are more modern way to achieve this with less boilder-plate?


Solution

  • Instead of checking if A implements a protected member function in your concept, you could check if this->A::fooImpl() is a valid statement for B.

    To do this, capture this in your visitor lambda:

    template <typename... T>
    struct B : T... {
        void foo() {
            auto visitor = [this] <typename U>(U&) {
                if constexpr (requires { this->U::fooImpl(); }) this->U::fooImpl();
            };
            (visitor(static_cast<T&>(*this)), ...); // note: argument just passed for template argument deduction
        }
    };
    

    https://godbolt.org/z/s5Yohd9sM

    Note that if you don't add U& as an argument to your lambda, the fold expression becomes (visitor.template operator()<T>(), ...); which is a bit awkward IMHO. Having a private template <typename U> void call_base_foo_impl() in B instead of the lambda might improve readability as well.