Вызов потенциальных функций-членов защищенного базового класса с переменным числом вариантов

Для контекста: я создаю миксин 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. Ожидается ли это? Я предполагаю это, следуя обсуждению Доступ к защищенному конструктору базового класса, которое также применимо к моему случаю, не так ли?

У меня есть два решения, которые я считаю неэлегантными.

  1. Идем по пути CRTP и пересылаем производный класс в базу, чтобы сделать производный другом.
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)), ...);
    }
};

Богболт Это работает, но мне интересно, есть ли лучший способ. Мне также приходится реорганизовать всю сигнатуру шаблона моего класса, что не так уж приятно.

  1. Скрытие базового класса 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)), ...);
    }
};

Богболт

Это тоже работает, но я также думаю, что, возможно, я переусердствовал. В любом случае, функция-член базового класса теперь также скрыта от пользователя. Есть ли более приятный способ добиться этого? Спасибо!

Обновлено: Рефакторинг последнего вопроса. Есть ли более современный способ добиться этого с меньшим количеством шаблонов?

«Есть ли лучший способ добиться этого?..» «Лучше/лучше» означает разные вещи для разных людей.

user12002570 08.03.2024 08:44

Это верно. Что тоже не очень точно, но мне интересно, существует ли более современное решение на C++ с меньшим количеством шаблонов.

rath3t 08.03.2024 08:48

Я думаю, что вам все равно придется сделать что-то подобное. Невозможно создать концепцию, которая проверяет, реализует ли A foo, когда foo защищен.

Pepijn Kramer 08.03.2024 09:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
3
122
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Не уверен, что он вам покажется лучше, но вот:

#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 08.03.2024 15:48

@Jarod42 Джарод42 Верно, спасибо. Старые привычки... Я обновил ответ.

bbalazs 08.03.2024 16:53
Ответ принят как подходящий

Вместо того, чтобы проверять, реализует ли 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 вместо того, чтобы передать его в виде аргумента.

Marek R 08.03.2024 11:27

Вы можете сделать это и перебрать пакет T... напрямую: ([&]{ if constexpr (requires { this->T::fooImpl(); }) this->T::fooImpl(); }(), ...)

Artyer 08.03.2024 11:27

@MarekR this фиксируется, он передается исключительно для целей вывода аргументов шаблона (его можно превратить в параметр U* и вместо него, я думаю, можно использовать static_cast<T*>(nullptr), но это не имеет особого значения)

Artyer 08.03.2024 11:29

Я имел в виду более элегантное решение. Рефакторинг из лямбды в полноценную функцию, где в качестве шаблона нужно передать только T, тогда идеален.

rath3t 10.03.2024 10:49

Проблема в том, что вам нужен оператор области действия вместо приведения 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

Я имел в виду более элегантное решение. Спасибо.

rath3t 10.03.2024 10:47

Другие вопросы по теме

Можно ли найти местоположение приостанавливающего выражения co_await из std::coroutine_handle?
Существуют ли требования к созданному по умолчанию состоянию std::forward_iterator для std::ranges?
Chrono: разные выходные данные для одной и той же программы с использованием gcc
Разбор проблемы с датой и временем с использованием хронографа
Захват этого и пересылка лямбды на другую лямбду в сочетании с fmt::join
Какова продолжительность хранения и время существования параметра шаблона, не являющегося типом, и как его можно использовать для вычислений во время компиляции?
Концепции C++20 и неполные типы
Адрес дезинфицирующего стека-use-after-scope с помощью std::format_args
Использование константных выражений в качестве параметра шаблона с использованием параметра функции: какой компилятор прав?
Проблема издевательства над невиртуальными функциями с помощью идиомы NVI