Для контекста: я создаю миксин CRTP для своего производного класса.
Теперь я хочу вызвать все функции-члены защищенного базового класса, если они существуют. Я хочу, чтобы функции-члены базового класса не были доступны пользователю.
Для этого у меня есть следующий код:
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;
}
Богболт
Этот код не вызывает fooImpl из C (как ожидалось), а также из A.
Я предполагаю, что это связано с static_cast, который удаляет protected видимость fooImpl. Ожидается ли это? Я предполагаю это, следуя обсуждению Доступ к защищенному конструктору базового класса, которое также применимо к моему случаю, не так ли?
У меня есть два решения, которые я считаю неэлегантными.
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)), ...);
}
};
Богболт Это работает, но мне интересно, есть ли лучший способ. Мне также приходится реорганизовать всю сигнатуру шаблона моего класса, что не так уж приятно.
foo путем вызова соглашения и переименования fooImpl в 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)), ...);
}
};
Это тоже работает, но я также думаю, что, возможно, я переусердствовал. В любом случае, функция-член базового класса теперь также скрыта от пользователя. Есть ли более приятный способ добиться этого? Спасибо!
Обновлено: Рефакторинг последнего вопроса. Есть ли более современный способ добиться этого с меньшим количеством шаблонов?
Это верно. Что тоже не очень точно, но мне интересно, существует ли более современное решение на C++ с меньшим количеством шаблонов.
Я думаю, что вам все равно придется сделать что-то подобное. Невозможно создать концепцию, которая проверяет, реализует ли A foo, когда foo защищен.





Не уверен, что он вам покажется лучше, но вот:
#include <iostream>
struct A {
protected:
void foo()
{
std::cout << "A::foo" << std::endl;
}
};
struct C {};
template <typename... T>
struct B : private T...
{
void foo()
{
// [[maybe_unused]] auto list = std::initializer_list<bool>{ (callFoo<T>(), true)...}; // pre-17
(callFoo<T>(), ...); // fold-expression
}
private:
template <typename U>
void callFoo()
{
if constexpr (requires {U::foo(); })
{
U::foo();
}
}
};
int main()
{
{
B<A, C> b;
b.foo();
}
{
B<> b;
b.foo();
}
return 0;
}
Хитрость std::initializer_list заключалась в расширении до C++17, поскольку у нас есть просто (callFoo<T>(), ...);.
@Jarod42 Джарод42 Верно, спасибо. Старые привычки... Я обновил ответ.
Вместо того, чтобы проверять, реализует ли A защищенную функцию-член в вашей концепции, вы можете проверить, является ли this->A::fooImpl() допустимым оператором для B.
Для этого зафиксируйте this в лямбде посетителя:
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
Обратите внимание: если вы не добавите U& в качестве аргумента к своей лямбде, выражение сгиба станет (visitor.template operator()<T>(), ...);, что, ИМХО, немного неудобно. Наличие приватного template <typename U> void call_base_foo_impl() в B вместо лямбды также может улучшить читаемость.
вы можете захватить this вместо того, чтобы передать его в виде аргумента.
Вы можете сделать это и перебрать пакет T... напрямую: ([&]{ if constexpr (requires { this->T::fooImpl(); }) this->T::fooImpl(); }(), ...)
@MarekR this фиксируется, он передается исключительно для целей вывода аргументов шаблона (его можно превратить в параметр U* и вместо него, я думаю, можно использовать static_cast<T*>(nullptr), но это не имеет особого значения)
Я имел в виду более элегантное решение. Рефакторинг из лямбды в полноценную функцию, где в качестве шаблона нужно передать только T, тогда идеален.
Проблема в том, что вам нужен оператор области действия вместо приведения this к соответствующему типу. При использовании указателя приведения вам необходим публичный доступ.
У меня есть хорошее решение на C++17 (C++17, поскольку я использовал выражение сгиба, его будет легко настроить на C++11):
template <typename... T>
struct B : T... {
void foo()
{
(invokeFooImpl<T>(1), ...);
}
private:
template <typename Base>
auto invokeFooImpl(int) -> decltype(Base::fooImpl())
{
std::cout << __PRETTY_FUNCTION__ << '\n';
Base::fooImpl();
}
template <typename Base>
auto invokeFooImpl(char)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
}
};
https://godbolt.org/z/avExz4M9s
Или версия C++20 без лямбды:
template <typename... T>
struct B : T... {
void foo()
{
(invokeFooImpl<T>(), ...);
}
private:
template <typename Base>
auto invokeFooImpl()
{
std::cout << __PRETTY_FUNCTION__ << '\n';
if constexpr (requires { this->Base::fooImpl(); }) Base::fooImpl();
}
};
https://godbolt.org/z/hvEb83zro
Я имел в виду более элегантное решение. Спасибо.
«Есть ли лучший способ добиться этого?..» «Лучше/лучше» означает разные вещи для разных людей.