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.
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.
foo
s 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)), ...);
}
};
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?
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.