Jump to content
  • Advertisement
Sign in to follow this  
staticVoid2

Convenient approach to composite pattern in C++

This topic is 1230 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

 

I have a class (A) which contains 'Listeners' or 'Observers' which need to be informed when certain things happen in class A. This currently involves iterating over all listeners and calling a certain notification method for example:

for(Listener listener : listeners)
{
   listener->InformOfSomething(1, 2, 3, 4);
}

I was wondering if there is any convenient way in c++11 to reduce this to a one-liner without having to create additional functions or use lambdas (or even remove the brackets ;)

 

The approach I'm looking for would be something similar to jQuery where functions act on collections of objects as opposed to single items. For e.g. maybe something as simple as this:

$(listeners).InformOfSomething(1, 2, 3, 4);

(please forgive the shameless copy of jQuery syntax)

 

Where the $ syntax acts in a similar way to range-based for loops and requires begin() and end() functions on the 'listeners' param which then iterates the collection and substitutes the item in the collection with first parameter of the proceeding function (or more specifically the 'this' pointer when using a class member function).

 

Anything like this in c++11 or any better way to do it?

 

Thanks.

Share this post


Link to post
Share on other sites
Advertisement
You could write a horrible macro to condense your loop to:
DOLLAR(listeners, InformOfSomething(1, 2, 3, 4));

:D (obviously call it something other than $ :lol:)

Share this post


Link to post
Share on other sites
It may be worth considering a more off-the-shelf solution to the problem, such as boost::signals2.
 
As for the code in question, C++'s (sometimes questionable) rules around braces and line breaks allow for an obvious one-liner:
for (Listener listener : listeners) listener->InformOfSomething(1, 2, 3, 4);
If you are willing to allow the use of lambdas, std::for_each becomes an option:
std::for_each(listeners.begin(), listeners(), [](Listener &l) {l->InformOfSomething(1, 2, 3, 4);});

There isn't any standard way to forward functions called against containers to the members of those containers (and this is in general a *very* tricky topic for compiled languages). Edited by swiftcoder

Share this post


Link to post
Share on other sites

Just write some templated helper functions, if std::for_each() doesn't do what you want.

 

Feel free to take mine. The header needs to be cleaned up a bit, but meh.

ForEach(array, DoSomething, 17, "Text"); //Global functions, functors, or lambdas.
ForEach(array, &Element::DoSomething, 17, "Text"); //Member functions
ForEachPtr(arrayOfPtrs, &Element::DoSomething, 17, "Text"); //Vector of smart pointers.

If you really wanted to, you could probably cobble together something that looked like this:

ForEach(array, &Element::DoSomething)(17, "Text"); 

Or this:

ForEach(array).(&Element::DoSomething, 17, "Text");  

...but I don't think this is possible:

ForEach(array).DoSomething(17, "Text"); //Not possible.

Not in straight C++ anyway - with the preprocessor you could do:

#define ForEach(container) for(auto &element : container) element

ForEach(arrayOfObjects).DoSomething(17, "Test");

[ideone]

 

You'd need a separate macro for const iteration. I'd stick to the templated functions, though.

Edited by Servant of the Lord

Share this post


Link to post
Share on other sites

#include <iostream>
#include <vector>

class Listener {
	public:
		void InformOfSomething(int a, int b, int c, int d) {
			std::cout << "Thank you for informing me about " << a << ", " << b << ", " << c << ", " << d << '\n';
		}
};

template<typename C, typename F, typename ...Args>
static inline void callVector(C &&it, F &&f, Args&& ...args) {
	for(auto &t : it)
		(t.*f)(std::forward<Args>(args)...);
}

int main() {
	std::vector<Listener> listeners(3);

	callVector(listeners, &Listener::InformOfSomething, 7, 8, 9, 0);
}

Edited by Erik Rufelt

Share this post


Link to post
Share on other sites

Its probably something of an oversight that there's no version of for_each that simply takes a container and a function to apply to all its elements -- it isn't much more inconvenient to simply use the iterator-pair version, or a short range-for form, like "for (auto l in listeners) l->do_stuff(1,2,3); but a version of for_each that doesn't use an iterator pair would eliminate unnecessary verbosity of the most-common use case:

 

Luckily, with non-member begin() and non-member end(), I think (that is to say **disclaimer** no warranty expressed or implied) you can implement this easily in those terms and in the terms of the existing iterator-pair version of for_each. This is not tested, but it would look something like this:

 

template<class Input, class Function>

Function for_each(Input in, Function fn)

{

    return for_each(std::begin(in), std::end(in), fn);

}

 

and usage would look something like so:

 

for_each(listeners, [](Listener &l) {l->InformOfSomething(1, 2, 3, 4);}

 

I think then, that you could reduce out the lambda with a function object template of some kind -- something like a "call_member_with_args" template, who's usage could look something like this:

 

for_each(listners, call(Listener::InformOfSomething)(1,2,3,4));

 

 

But I'll leave implementation as an exercise to the reader. (I think probably you could use std::function, but its not lightweight -- I wonder, though, if its exactly as heavy as an encapsulated member function anyways)

 

 

[EDIT] ... and Erik's C++11 variadic-template-fu is both stronger and quicker than my outdated C++06 template-fu

Edited by Ravyne

Share this post


Link to post
Share on other sites

template<typename C, typename F>
static inline Caller<C, F> operator->*(C &c, F &&f) {


Yeah, okay. I'm totally okay with what just happened and am totally fine with how readable this is.

static ::operator->*... Yup!

Share this post


Link to post
Share on other sites
I've been playing with Erik's code a bit, and another candidate is the almost-never overloaded comma operator. I didn't post the code since I'm unfamiliar with troubles that might arise. I suspect its safe, as comma has lowest precedence, and this use is similar to other uses in C++ DSL implementations, or as its used to collect terms in parts of boost, I'm just unfamiliar with potential hazards that might be found.

Usage looks like:

(Listeners, &Listener::InformOfSomething)(1,2,3,4);

Which doesn't read too poorly is you read the first set of parens as something like "cartesean product of this collection and this member function"

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!