1

This program compiles, but the boost::any cast fails. I suspect that slicing a template class this way confuses pointer arithmetic. The idea is that what is stored in the container

std::vector<boost::any> pressures;

are of different types, for example

Pressure<Printer>, or Pressure<Printer, Printer> etc.

Since I lose the type by storing it in a boost::any, I need to call Change without having to know the actual number of observers there are on a given pressure. I tried to solve it through polymorphism and virtual methods, but at least this attempt doesn't work.

Any suggestions?

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

enum class EventType {UNKNOWN};


// Note: All Observers must implement OnNotify for any subject types they wish to observe
//       Any unimplemented subject types that are used will result in a compiler error
template <typename Base> class Observer
{
public:
    Observer() : obsID_(obsIDTracker_++) {}
    template <typename T> void OnNotifyImpl(T &subject, EventType event)
    {
        static_cast<Base *>(this)->OnNotify(subject, event);
    }
    int GetID() const
    {
        return obsID_;
    }
private:
    int obsID_;
    static int obsIDTracker_;
};
template <typename base> int Observer<base>::obsIDTracker_ = 0;

// Recursive helper structs for implementing calls to all observers held within subjects
template <int N, typename T, typename... Args> struct NotifyHelper
{
    static void NotifyImpl(T &subject, EventType event,
                           std::tuple<Args...> &obs)
    {
        std::get<sizeof...(Args) - N>(obs).OnNotifyImpl(subject, event);
        NotifyHelper<N - 1, T, Args...>::NotifyImpl(subject, event, obs);
    }
};
template <typename T, typename... Args> struct NotifyHelper<0, T, Args...>
{
    static void NotifyImpl(T &subject, EventType event,
                           std::tuple<Args...> &obs) {}
};

// See MakeSubject function for instance usage
template <typename T, typename... Obs> class Subject
{
public:
    static const int NumberOfObservers = sizeof...(Obs);
    Subject(std::tuple<Obs &...> &&obs) : observers(obs) {}
    void NotifyAll(EventType event)
    {
        NotifyHelper<NumberOfObservers, T, Obs &...>::NotifyImpl(
            *static_cast<T *>(this), event, observers);
    }

private:
    std::tuple<Obs &...> observers;
};

class PressureInterface
{
public:
    virtual ~PressureInterface() {}
    virtual void Change(int value) {}
};

// CRTP Abstract Base class for implementing static subject.
// Example Subclass Usage -- Pressure Sensor:
template <typename... Obs>
class Pressure : public PressureInterface, public Subject<Pressure<Obs...>, Obs...>
{
public:
    typedef Subject<Pressure<Obs...>, Obs...> BaseType;
    Pressure(std::tuple<Obs &...> &&observers, int pressure)
        : BaseType(std::move(observers)), pressure_(pressure) {}
    virtual void Change(int value)
    {
        pressure_ = value;
        this->NotifyAll(EventType::UNKNOWN);
    }
    int GetPressure() const
    {
        return pressure_;
    }

private:
    int pressure_;
};

// CRTP Abstract Base class for implementing static subject.
// Example Subclass Usage -- Printing Observer:
class Printer : public Observer<Printer>
{
public:
    Printer() : timesTriggered_(0) {}
    template <typename... Args>
    void OnNotify(Pressure<Args...> &subject, EventType event)
    {
        std::cout << "Observer ID: " << this->GetID() << std::endl;
        switch (event)
        {
        case EventType::UNKNOWN:
        {
            std::cout << "Unknown Event -- Event #" << timesTriggered_++
                      << std::endl;
            std::cout << "Pressure: " << subject.GetPressure() << std::endl;
            break;
        }
        default:
        {
            break;
        }
        }
    }

private:
    int timesTriggered_;
};


// Binding function for use with MakeSubject
//   Arguments: observer objects to observe subject notifications
//   Return:    tuple of references to observers
template <typename... Obs> std::tuple<Obs &...> BindObservers(Obs &... obs)
{
    return std::tuple<Obs &...>(obs...);
}

// Creator to ease subject creation
//   Template Arguments: Subject subclass type
//   Arguments: Result from BindObservers
//              Any constructor arguments for Subject subclass
//   Return:    Subject subclass
// Example Usage:
// auto pressure = MakeSubject<Pressure>(BindObservers(printerObs), initialPressure);
template <template <typename...> class T, typename... Args, typename... Obs>
T<Obs...> MakeSubject(std::tuple<Obs &...> &&obs, Args &&... args)
{
    return T<Obs...>(std::move(obs), args...);
}

#include <boost/any.hpp>

int main()
{
    std::vector<boost::any> pressures;

    Printer printerObs1;
    Printer printerObs2;

    const int initialPressure = 1;

    auto pressure = MakeSubject<Pressure>(
                        BindObservers(printerObs1, printerObs2), initialPressure);

    pressures.push_back(pressure);

    pressure.Change(12);

    decltype(pressure) *p = boost::any_cast<decltype(pressure)>(&pressures[0]);

    p->Change(1999);

    PressureInterface *qip = boost::any_cast<PressureInterface>(&pressures[0]); //This cast returns nullptr

    std::cout << "The cast works\n";

    if(nullptr != qip) 
        qip->Change(2001);
}

Edit

My first attempt at storing the address of the Change function:

std::vector<std::function<boost::any *>> pressures;

How do I push_back the address of the function? This doesn't work:

pressures.push_back(std::function<decltype(&pressure.Change>);

/home/idf/Documents/OrigObserverExam/ObserverExample.cpp|157|error: ISO C++ forbids taking the address of a bound member function to form a pointer to member function.  Say '&Pressure<Printer, Printer>::Change' [-fpermissive]|

and then how do I extract it?

std::function<void(int)> *qip = boost::any_cast<std::function<void(int)>*>(&(pressures[0].Change));

std::cout << "The cast works\n";

if(nullptr != qip)
    *qip(2001);

Edit 2

When I add the code suggested, I get an error:

/home/idf/Documents/OrigObserverExam/ObserverExample.cpp|167|error: 'decay_t' is not a member of 'std'|

#include <type_traits>
#include <boost/any.hpp>

struct changable {
  boost::any data;
  using do_change = void(*)(boost::any*, int);
  do_change f = nullptr;
  void change(int x) {
    if (f) f(&data, x);
  }
  template<class T>
  static do_change type_erase_change() {
    return [](boost::any* a, int x){
      T* t = boost::any_cast<T>(a);
      if (t) t->Change(x);
    };
  }
  template<class T>
  changable( T&& t ):
    data(std::forward<T>(t)),
    f( type_erase_change<std::decay_t<T>>() )
  {}
  changable(changable const&)=default;
  changable(changable &&)=default;
  changable()=default;
};

Edit 3 C++14 installed:

How do I use this struct? I am able to say:

 std::vector<changable> pressures;

and I am able to push_back a pressure

pressures.push_back(pressure);

However, I am uncertain how to call say pressures[0].Change(1999). If I say I get the error given:

pressures[0].Change(2000); 

ObserverExample.cpp|199|error: '__gnu_cxx::__alloc_traits<std::allocator<changable> >::value_type' has no member named 'Change'
Ivan
  • 7,448
  • 14
  • 69
  • 134
  • Note that boost::any_cast can return null on failure - it worth checking if qip is null before calling qip->Change(2001); – Ilya Kobelevskiy Feb 23 '15 at 17:08
  • Hmmm, I just added the check and it seems as if thee boost::any_cast(&pressures[0]); is returning nullptr. I guess that answers that. Now the question is why is it not allowing me to cast it to a base class? – Ivan Feb 23 '15 at 17:27
  • It can't, because it doesn't know to check that it houses a `decltype(pressure)` object when you ask it for `PressureInterface`. `boost::any` holds by pointer an implementation detail that can give it the `std::type_info` of the concrete type of its value; this enables you to ask "do you hold an object of this type?" and `boost::any` to answer yes or no by checking whether they have the same `type_info` object. It does not enable you to ask "is the type you hold derived from this type?" – Wintermute Feb 23 '15 at 17:39

1 Answers1

2

boost::any allows you to type cast back to the exact same type you put in. Not a parent type, the same type.

If you want to type erase invoking a method, try std::function<void()> or std::function<void(boost::any*)>.

Here is a type eraser of change(int) and a boost::any bundled together:

struct changable {
  boost::any data;
  using do_change = void(*)(boost::any*, int);
  do_change f = nullptr;
  void change(int x) {
    if (f) f(&data, x);
  }
  template<class T>
  static do_change type_erase_change() {
    return [](boost::any* a, int x){
      T* t = boost::any_cast<T>(a);
      if (t) t->Change(x);
    };
  }
  template<class T>
  changable( T&& t ):
    data(std::forward<T>(t)),
    f( type_erase_change<std::decay_t<T>>() )
  {}
  changable(changable const&)=default;
  changable(changable &&)=default;
  changable()=default;
};

there is no need for an interface class that exposes Change. So long as the type passed to the above type-eraser has a Change(int) method, all is good.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Gotcha. What you are suggesting makes sense. I can't seem to get the syntax of storing the std::function correct though. Can you supply a small block of code that shows how to store the function pointer in an std::vector (or equivalent container) and then extract it to call the Change method through the PressureInterface virtual method? – Ivan Feb 23 '15 at 19:56
  • See the Edit section of the original post. This is where I am stuck. – Ivan Feb 23 '15 at 20:08
  • @Ivan explicit struct added that type erases both the type, and invoking `Change` on the type. – Yakk - Adam Nevraumont Feb 23 '15 at 20:10
  • Yakk, see Edit 2. Does this require a specific version of g++? I have 4.8.2-19 – Ivan Feb 23 '15 at 20:25
  • @Ivan `templateusing decay_t = typename std::decay::type;` C++14 feature. Either write `typename std::decay::type` directly, or write an alias like the above. – Yakk - Adam Nevraumont Feb 23 '15 at 20:30
  • Ok finally was able to upgrade the compiler so that it understands c++14. I get by the code above fine. One last question, see Edit 3 above. – Ivan Feb 24 '15 at 19:23
  • @Ivan Try `.change` not `.Change`. Or change the case of `change` above. – Yakk - Adam Nevraumont Feb 24 '15 at 19:34