3

EDIT:

  • I want to implement an subject - observer pattern such that I can call notify<SUBJECT>() from anywhere and many update<SUBJECT>() fuctions get called
  • A subject is a struct type. There can be several of these subjects
  • I want to be able to put an observer update function anywhere in the code (in different compilation units)
  • The notify function needs to known which update functions to call at compile-time
  • It would be good if the notify functions can be connected to the update functions automatically

I am trying to implement a kind of Observer pattern, the notification mechanism. I currently use a run-time implementation that allows one to attach itself to a vector. However, all "attach" calls are made during startup so it can also be done (and I want in to be done) at compile-time.

  • My first idea was without the int template parameter which leads to redefinition error from the linker. It would be nice if you can tell the linker to keep all of them and call all of them, but I'm pretty sure that this cannot be done.
  • Now I use weak linkage and an int template parameter to make the methods unique. However, that is not as comfortable as I want it. Having to remember all the numbers across the whole codebase. At least, I get hard errors when I use the number twice.
  • I'm not so deep into template metaprogramming, but I think it gets complicated when it comes to different compilation units (but I can also switch to a single compilation unit)
  • I'd like to avoid boost but C++17 is perfectly fine.

This is the common header:

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);
}

Let's say we have two compile units:

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;
}

I need to get rid of the numeric iteration in different compile units but can still have multiple implementations of the update function of the same type. All "connected" at compile-time to be called at run-time.

Marc Lühr
  • 75
  • 5
  • Probably not possible. It looks to me like you basically want to change the behavior of `notify`, without changing `notify`, on a per-compilation unit basis. My understanding is that this can only be done through runtime mechanisms. I'll wait and see what the experts have to say about it. – Not a real meerkat Jun 08 '19 at 13:55
  • Please rephrase your question, as it's hard to understand, what exactly you want. C++ doesn't have any of iterate-over-all-defined-symbols mechanics yet, thus if u want to achieve get-all-defined-update-functions-and-call-them-at-compile-time, then it's not possible. You need to create some sort of template list and call it's members manually. – Radosław Cybulski Jun 08 '19 at 15:41
  • Perhaps your question would be easier to understand if you focused on what you are trying to accomplish, rather than focusing on the approaches that you do not like? A nice problem/requirement statement, instead of jumping into failed solutions? – JaMiT Jun 08 '19 at 17:36
  • Thank you for your feedback, I will try to make it clearer. – Marc Lühr Jun 08 '19 at 18:58

2 Answers2

2

I'm not sure I understand your question completely, but if your restriction on observers having the same type is not a hard restriction, you can do some meta programming magic to iterate a list of types and call a static update() on each of them, without the manual unrolling.

Here's an example:

#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);
}

Output:

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

See it live!


However, if you just want to call Observer::update iteratively, you can just implement notify as follows:

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);
}

If you are worrying about what happens on different translation units, you just need to remember that templates are code generators: Ultimately, the code generated will be the same as if you did the unrolled loop by hand, and, if that works, this should work as well.

Live example

Not a real meerkat
  • 5,604
  • 1
  • 24
  • 55
  • Thenk you for your answer, it looks that you did not understand my question too bad. It looks promising, I will try to bring it together and come back later. – Marc Lühr Jun 08 '19 at 19:07
1

Maybe something like this:

#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;
}

The list of calls to the obsevers might be unrolled and inlined by the optimizer.

Marko Mahnič
  • 715
  • 6
  • 11