Есть ли умный способ реализовать шаблон наблюдателя полностью статично (во время компиляции) - PullRequest
2 голосов
/ 08 июня 2019

EDIT:

  • Я хочу реализовать шаблон субъекта-наблюдателя так, чтобы я мог вызывать notify<SUBJECT>() из любой точки мира, и многие update<SUBJECT>() функции будут вызываться
  • Субъект является структурным типом. Таких предметов может быть несколько
  • Я хочу иметь возможность добавлять функцию обновления наблюдателя в любом месте кода (в разных единицах компиляции)
  • Функция уведомления должна знать, какие функции обновления нужно вызывать во время компиляции
  • Было бы хорошо, если бы функции уведомлений можно было автоматически подключать к функциям обновления

Я пытаюсь реализовать некий шаблон Observer, механизм уведомлений. В настоящее время я использую реализацию во время выполнения, которая позволяет присоединиться к вектору. Однако все вызовы «присоединения» выполняются во время запуска, так что это также может быть сделано (и я хочу, чтобы это было сделано) во время компиляции.

  • Моя первая идея была без параметра шаблона int, который приводит к ошибке переопределения компоновщика. Было бы неплохо, если бы вы могли сказать компоновщику оставить их всех и вызвать их все, но я почти уверен, что это невозможно.
  • Теперь я использую слабую связь и параметр шаблона int, чтобы сделать методы уникальными. Однако это не так удобно, как я хочу. Необходимость запомнить все числа по всей базе кода. По крайней мере, я получаю серьезные ошибки, когда использую число дважды.
  • Я не очень глубоко разбираюсь в шаблонном метапрограммировании, но я думаю, что это становится сложным, когда речь идет о разных модулях компиляции (но я также могу переключиться на один модуль компиляции)
  • Я бы хотел избежать повышения, но C ++ 17 прекрасно.

Это общий заголовок:

struct Test {
    int value;
};

namespace Observer
{
    template<typename T, int N>
    void update(decltype(T::value));

    /*
     * This ìs a function dummy which can be overloaded with the
     * real implementation. The linker overrides them and kicks out
     * the unoverridden functions since they do nothing.
     */
    template<typename T, int N>
    void __attribute__((weak)) update(decltype(T::value)) {}
}

template<typename T>
void notify(decltype(T::value) value)
{
    Observer::update<T, 1>(value, value != old);
    Observer::update<T, 2>(value, value != old);
    Observer::update<T, 3>(value, value != old);
}

Допустим, у нас есть два модуля компиляции:

template<>
void Observer::update<Test, 1>(decltype(Test::value) value)
{
    std::cout << "update: " << value << std::endl;
}
template<>
void Observer::update<Test, 2>(decltype(Test::value) value)
{
    std::cout << "update: " << value << std::endl;
}

Мне нужно избавиться от числовой итерации в разных единицах компиляции, но все же может иметь несколько реализаций функции обновления одного и того же типа. Все «подключенные» во время компиляции будут вызываться во время выполнения.

Ответы [ 2 ]

1 голос
/ 08 июня 2019

Я не уверен, что полностью понимаю ваш вопрос, но если ваше ограничение для наблюдателей одного типа не является жестким, вы можете применить магию метапрограммирования для итерации списка типов и вызова статического update()на каждом из них, без ручного развертывания.

Вот пример:

#include <iostream>
#include <tuple>
#include <utility>

// A list of types
template<typename... Ts> class observers{
    template<std::size_t I, typename...>
    struct at_impl{};

    template<std::size_t I, typename T, typename... Others>
    struct at_impl<I, T, Others...> {
        using type = typename at_impl<I-1, Others...>::type;
    };

    template<typename T, typename... Others>
    struct at_impl<0, T, Others...> {
        using type = T;
    };

public:

    // A way of getting the nth-type listed
    template<std::size_t I>
    using at = typename at_impl<I, Ts...>::type;

    constexpr inline static std::size_t size = sizeof...(Ts);
};

// Our notification function will iterate a list of observers, and call update() on each one.

template<typename ObserverList, std::size_t I = 0>
std::enable_if_t< ObserverList::size <= I, void>
notify_impl(int v) {}

template<typename ObserverList, std::size_t I = 0>
std::enable_if_t< I < ObserverList::size, void> 
notify_impl(int v) {
    using T = typename ObserverList::template at<I>;
    T::update(v);
    notify_impl<ObserverList, I+1>(v);
}

template<typename ObserverList>
void notify(int v) {
    notify_impl<ObserverList, 0>(v);
}

// Let's define some observers...

struct FirstObserver {
    static void update(int v){std::cout << "first observer notified with value = " << v << '\n';}
};

struct SecondObserver {
    static void update(int v){std::cout << "second observer notified with value = " << v << '\n';}
};

struct ThirdObserver {
    static void update(int v){std::cout << "third observer notified with value = " << v << '\n';}
};

// ... and put those observers on a list.
using ObserverList = observers<FirstObserver, SecondObserver, ThirdObserver>;

int main() {
    notify<ObserverList>(0);
}

Вывод:

first observer notified with value = 0
second observer notified with value = 0
third observer notified with value = 0

Смотрите вживую!


Однако, если вы просто хотите вызвать Observer::update итеративно, вы можете просто реализовать notify следующим образом:

template<typename T, std::size_t I, std::size_t MAX>
std::enable_if_t< MAX <= I, void>
notify_impl(decltype(T::value)) {}

template<typename T, std::size_t I, std::size_t MAX>
std::enable_if_t< I < MAX, void> 
notify_impl(decltype(T::value) v) {
    Observer::update<T, I>(v);
    notify_impl<T, I+1, MAX>(v);
}

template<typename T>
void notify(decltype(T::value) v) {
    // Assuming we have 3 observers
    notify_impl<T, 0, 3>(v);
}

Если вы беспокоитесь о том, что происходит на разныхединицы преобразования, вам просто нужно помнить, что шаблоны являются генераторами кода: в конечном счете, сгенерированный код будет таким же, как если бы вы выполняли развернутый цикл вручную, и, если это работает, это должно работать также.

Живой пример

1 голос
/ 08 июня 2019

Может быть что-то вроде этого:

#include <iostream>
#include <array>
#include <functional>

template<typename Container, typename T>
void notify( Container& observers, T value )
{
   for ( auto& observer : observers )
      observer( value );
}

// updateX can be defined in another unit but exposed in a header
void update1( int i )
{
   std::cout << "observer 1:" << i << std::endl;
}

void update2( int i )
{
   std::cout << "observer 2:" << i << std::endl;
}

const std::array<std::function<void(int)>, 2> observers {
   [](int i) { update1( i ); },
   [](int i) { update2( i ); }
};

int main()
{
   notify( observers, 3 );
   return 0;
}

Список обращений к наблюдателям может быть развернут и оптимизирован оптимизатором.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...