Order of decorations in Decorator Pattern - c++

Most of you know the pizza / cofee example for the decorator pattern.
Pizza* pizza1 = BigPizzaDecorator(MushromDecorator(SimplePizza()));
Pizza* pizza2 = MushromDecorator(BigPizzaDecorator(SimplePizza()));
the two object behave in a similar way, but not completely, in particular if you have non-commutative operation, for example:
BigPizzaDecorator::price() { return 10 + PizzaDecorator::price(); } // this is commutative
BigPizzaDecorator::name() { return "big " + PizzaDecorator::name(); } // this is not commutative
So the price for pizza1 and pizza2 are the same, but the name is not, for example the first should be "Big mushroom pizza", the second "Mushroom big pizza". The first is english correct (probably better would be "Big pizza with mushroom", but it's not so important).
The book "Head first" point out this problem with the Cofee example:
When you need to peek at multiple layers into the decorator chain, you
are starting to push the decorator beyond its true intent.
Nevertheless, such things are possible. Imagine a CondimentPrettyPrint
decorator that parses the final decription and can print “Mocha, Whip,
Mocha” as “Whip, Double Mocha.”
what is the best way to do that? (operator< ?)

Ive never known this sort of thing to be needed when using decorators. And I would think that if you need to do this, then you shouldn't be using decorators, especially as you're knowingly "pushing the decorator beyond it's intent".
I have had a stab at doing this, the code is below. Basically, I create a thin layer around the SimplePizza object that understands what the decorators need, then the decorators decorate that.
The main problem here, is that to maintain order in the output, you would have to maintain a relationship between decorators - which can quickly become a maintenance nightmare.
#include <iostream>
#include <queue>
#include <sstream>
struct name_part
{
std::string mName;
int mPriority;
name_part(const std::string& name, int priority)
: mName(name)
, mPriority(priority)
{
}
};
bool operator<(const name_part& a, const name_part& b)
{
return (a.mPriority < b.mPriority);
}
std::string priority_queueToString(const std::priority_queue<name_part>& orig)
{
std::ostringstream oss;
std::priority_queue<name_part> q(orig);
while (!q.empty())
{
oss << q.top().mName << " ";
q.pop();
}
return oss.str();
}
struct SimplePizza
{
virtual std::string name()
{
return "pizza";
}
};
struct SimplePizzaImplementer : SimplePizza
{
SimplePizza *mDecorated;
SimplePizzaImplementer()
: mDecorated(0)
{
}
SimplePizzaImplementer(SimplePizza *decorated)
: mDecorated(decorated)
{
}
virtual std::string name()
{
return priority_queueToString(nameParts());
}
virtual std::priority_queue<name_part> nameParts()
{
std::priority_queue<name_part> q;
if (mDecorated)
{
q.push(name_part(mDecorated->name(), 0));
}
return q;
}
};
struct MushroomDecorator : SimplePizzaImplementer
{
SimplePizzaImplementer *mDecorated;
MushroomDecorator(SimplePizzaImplementer *decorated)
: mDecorated(decorated)
{
}
virtual std::string name()
{
return priority_queueToString(nameParts());
}
virtual std::priority_queue<name_part> nameParts()
{
std::priority_queue<name_part> q = mDecorated->nameParts();
q.push(name_part("mushroom", 1));
return q;
}
};
struct BigDecorator : SimplePizzaImplementer
{
SimplePizzaImplementer *mDecorated;
BigDecorator(SimplePizzaImplementer *decorated)
: mDecorated(decorated)
{
}
virtual std::string name()
{
return priority_queueToString(nameParts());
}
virtual std::priority_queue<name_part> nameParts()
{
std::priority_queue<name_part> q = mDecorated->nameParts();
q.push(name_part("big", 2));
return q;
}
};
int main()
{
SimplePizzaImplementer *impl = new SimplePizzaImplementer(new SimplePizza());
SimplePizza *pizza1 = new MushroomDecorator(new BigDecorator(impl));
SimplePizza *pizza2 = new BigDecorator(new MushroomDecorator(impl));
std::cout << pizza1->name() << std::endl;
std::cout << pizza2->name() << std::endl;
}

In terms of where to put such code, have an overloaded operator<< is feasible.
I feel that the "pushing the decorator beyond it's intent" really needs emphasis here.
Would you really build a serious application whose functioning depends on parsing
"Mocha, Whip, Mocha"
and formulating
"Whip, Double Mocha"
Conceptually you are inferring semantics from an interface that is not published with that intent. The result will be very brittle, minor changes in implementations of decorators: "Yummy super mocha special" would break the parser. Adding new decorators would require unknown levels of change.

Related

Class design - use optionals? variants? be opaque?

I want to have a class for PCI bus locations. For the sake of discussion, these come in three forms:
[domain]:[bus]:[device].[function]
[domain]:[bus]:[device]
[bus]:[device].[function]
and let's say each field is a non-negative integral value (let's even say unsigned just to make things simple).
I'm scratching my head regarding how to define this class. I could use std::optionals for the domain and function fields; but then, they're not both optional. I could use a variant with 3 types, but then I need to define separate types, which overlap a lot. I could just hold 4 unsigneds and a 3-value enum for which format is in effect - but that's quite a bit of hassle, and I'd need getter and to make the class opaque. Same thing if I try to use a union somehow.
It seems like every choice I make, it's going to be an iffy class. How can I minimize my displeasure with it?
Note: Any language standard version is ok for the answer, although I doubt C++20 would give you anything.
Building upon my comment, I was wondering if something like this could work:
enum class pci_format { domain_function, domain, function };
template <pci_format E> struct tag { };
class pci_location {
public:
pci_location (tag<pci_format::domain_function>, unsigned domain, unsigned bus,
unsigned device, unsigned function)
: format_(pci_format::domain_function)
, domain_(domain)
, bus_(bus)
, device_(device)
, function_(function)
{ }
// Repeat for other values of pci_format.
pci_format format () const { return format_; }
bool has_domain () const {
return (format_ == pci_format::domain_function)
or (format_ == pci_format::domain);
}
unsigned domain () const {
if (not has_domain()) { throw std::runtime_error("Domain not available."); }
return domain_;
}
// Repeat for other fields.
private:
pci_format format_;
unsigned domain_;
unsigned bus_;
unsigned device_;
unsigned function_
};
You would basically create a specific constructor for each PCI "format". Of course you could also store each unsigned as an std::optional<unsigned>, but that would force users to "dereference" each optional even if they knew for sure that it must contain a value.
One way or another, they'll have to check what "format" the location is in, so it seems to me that using an enum for this is more user friendly. Then users only have to check once and know exactly which fields are available.
I guess you could layer a visitor on top of all this so they can simply provide code to execute for each "format":
struct pci_location_visitor {
virtual void visit (tag<pci_format::domain_function>, pci_location const & obj) = 0;
// Repeat for other enum values.
};
// Add to pci_location:
void accept (pci_location_visitor & visitor) {
switch (format_) {
case pci_format::domain_function:
return visitor.visit(tag<pci_format::domain_function>{}, *this);
default: throw std::runtime_error("Format not supported for visitation.");
}
}
Then on top of that you could create a visitor that can be constructed from a bunch of callables, i.e. lambdas, so that this all can be used like below:
pci_location const & loc = getIt();
auto printSomething = make_pci_location_visitor(
[](tag<pci_format::domain_function>, pci_location const & e) { std::cout << e.domain(); }
, [](tag<pci_format::domain>, pci_location const & e) { std::cout << e.bus(); }
, [](tag<pci_format::function>, pci_location const & e) { std::cout << e.function(); }
);
loc.accept(printSomething);
For an example of how such a visitor could be constructed, see the overloaded class in the std::visit example on cppreference.com.
As requested in comments... given that I have no particular requirements how the users would prefer to use this class, given C++14, I would be doing something generic along the lines of:
#include <array>
#include <climits>
#include <iostream>
#include <stdexcept>
class pci_location_t {
public:
struct dbdf {
unsigned int domain;
unsigned int bus;
unsigned int device;
unsigned int function;
};
struct dbd {
unsigned int domain;
unsigned int bus;
unsigned int device;
};
struct bdf {
unsigned int bus;
unsigned int device;
unsigned int function;
};
pci_location_t(dbdf v) : domain(v.domain), bus(v.bus), device(v.device), function(v.function) {}
pci_location_t(dbd v) : domain(v.domain), bus(v.bus), device(v.device), function(INVALID) {}
pci_location_t(bdf v) : domain(INVALID), bus(v.bus), device(v.device), function(v.function) {}
template <typename dbdf_f, typename dbd_f, typename bdf_f>
auto visit(dbdf_f dbdf_fun, dbd_f dbd_fun, bdf_f bdf_fun) const {
if (domain == INVALID) {
if (function == INVALID) {
throw std::domain_error("Wrong PCI location format");
}
return bdf_fun(bdf{bus, device, function});
} else if (function == INVALID) {
return dbd_fun(dbd{domain, bus, device});
} else {
return dbdf_fun(dbdf{domain, bus, device, function});
}
}
private:
friend pci_location_t invalid_location();
pci_location_t() : domain(INVALID), bus(INVALID), device(INVALID), function(INVALID) {}
const static unsigned int INVALID = UINT_MAX;
unsigned int domain;
unsigned int bus;
unsigned int device;
unsigned int function;
};
pci_location_t invalid_location() { return pci_location_t{}; }
int main() {
std::array<pci_location_t, 4> locations = {
pci_location_t(pci_location_t::dbdf{1, 2, 3, 4}),
pci_location_t(pci_location_t::dbd{1, 2, 3}),
pci_location_t(pci_location_t::bdf{2, 3, 4}),
invalid_location()
};
try {
for (auto& l : locations) {
l.visit(
[] (auto dbdf) {
std::cout << dbdf.domain << ":" << dbdf.bus << ":" << dbdf.device << "." << dbdf.function << std::endl;
},
[] (auto dbd) {
std::cout << dbd.domain << ":" << dbd.bus << ":" << dbd.device << std::endl;
},
[] (auto bdf) {
std::cout << bdf.bus << ":" << bdf.device << "." << bdf.function << std::endl;
}
);
}
std::cout << "Done!" << std::endl;
} catch(const std::exception& e) {
std::cout << e.what() << std::endl;
}
return 0;
}
(you can check it on Coliru).
Feel free to use optionals or a separate format field if you don't like special values.
I'd make both the domain and the function optional (I don't really care how, as long as it's effective), and just enforce the only-one-missing condition as a class invariant. That is, only the functions that can change any of the fields need to perform the check and signal possible errors back to the user. No need to bloat your code with variants, or with dynamically interpreted unsigned int arrays. KISS.

Correct way of unpacking operation type from network application

I come from python world, and as a weekend project I decided to write a simple UDP server in c++. I have a question regarding correct way of discovering the type of incoming request. My approach is to have a class for every possible type of request. Upon packet arrival I have to unpack it's OPID (operation id) and instantiate correct class. To do that I have to bind OPIDs with the classes, and the only way I'm familiar of doing this in c++ involves huge switch:case block. Doing this doesn't really feels right for me, also If I understand UncleBob correctly, this goes against few OOP practices. As code describes the best one's intentions, here's python equivalent of what I'm trying to do with c++.
class BaseOperation:
OPID = 0
def process(packet_data):
raise NotImplementedError("blah blah")
class FooOperation(BaseOperation):
OPID = 1
def process(packet_data):
print("Foo on the packet!")
class BarOperation(BaseOperation):
OPID = 2
def process(packet_data):
print("Bar on the packet!")
opid_mappings = {
FooOperation.OPID: FooOperation,
BarOperation.OPID: BarOperation
}
Somewhere in code handling the incoming packet
def handle_connection(packet):
try:
operation = opid_mappings[get_opid(packet)]()
except KeyError:
print("Unknown OPID")
return
operation.process(get_data(packet))
Really quick hack of object-based solution. This might not be the right way to go in our wonderful new C++11 world of std::function.
If the children of BaseOperation need to store state, go objects!
#include <iostream>
#include <map>
class BaseOperation
{
protected:
int OPID;
public:
virtual ~BaseOperation()
{
}
virtual int operator()() = 0;
};
class FooOperation:public BaseOperation
{
public:
static constexpr int OPID = 1;
FooOperation()
{
}
int operator()()
{
// do parsing
return OPID; // just for convenience so we can tell who was called
}
};
constexpr int FooOperation::OPID; // allocate storage for static
class BarOperation:public BaseOperation
{
public:
static constexpr int OPID = 2;
BarOperation()
{
}
int operator()()
{
// do parsing
return OPID; // just for convenience so we can tell who was called
}
};
constexpr int BarOperation::OPID; // allocate storage for static
std::map<int, BaseOperation*> opid_mappings{
{FooOperation::OPID, new FooOperation()},
{BarOperation::OPID, new BarOperation()}
};
int main()
{
std::cout << "calling OPID 1:" << (*opid_mappings[1])() << std::endl;
std::cout << "calling OPID 2:" << (*opid_mappings[2])() << std::endl;
for (std::pair<int, BaseOperation*> todel: opid_mappings)
{
delete todel.second;
}
return 0;
}
This also ignores the fact that there is probably no need for the map. If the OPIDs are sequential, a good ol' dumb array solves the problem. I like the map because it won't screw up if someone moves a parser handler or inserts one into the middle of the list.
Regardless, this has a bunch of memory management problems, such as the need for the for loop deleting the parser objects at the bottom of main. This could be solved with std::unique_ptr, but this is probably a rabbit hole we don't need to go down.
Odds are really good that the parser doesn't have any state and we can just use a map of OPIDs and std::function.
#include <iostream>
#include <map>
#include <functional>
static constexpr int FooOPID = 1;
int fooOperation()
{
// do parsing
return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation()
{
// do parsing
return BarOPID;
}
std::map<int, std::function<int()>> opid_mappings {
{FooOPID, fooOperation},
{BarOPID, BarOperation}
};
int main()
{
std::cout << "calling OPID 1:" << opid_mappings[1]() << std::endl;
std::cout << "calling OPID 2:" << opid_mappings[2]() << std::endl;
return 0;
}
And because the parser's are kind of useless if you aren't passing anything in, one last tweak:
#include <iostream>
#include <map>
#include <functional>
struct Packet
{
//whatever you need here. Probably a buffer reference and a length
};
static constexpr int FooOPID = 1;
int fooOperation(Packet & packet)
{
// do parsing
return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation(Packet & packet)
{
// do parsing
return BarOPID;
}
std::map<int, std::function<int(Packet &)>> opid_mappings {
{FooOPID, fooOperation},
{BarOPID, BarOperation}
};
int main()
{
Packet packet;
std::cout << "calling OPID 1:" << opid_mappings[1](packet) << std::endl;
std::cout << "calling OPID 2:" << opid_mappings[2](packet) << std::endl;
return 0;
}

Best practice for forwarding messages between typed actors in the C++ Actors Framework?

I'm trying to hand off some work from one typed actor to another. The CAF user manual indicates that this can be done using the forward_to method. That method looks like it is only available to actors that are explicitly of the event_based_actor type. However, forward_to appears to be a thin wrapper over the forward_current_message method, which is defined for all actors of the local_actor type. Therefore, I assume it's okay to call forward_current_message directly?
Also, in order to get message forwarding working with typed actors, I still had to return a response from the intermediate actor. That actor's response seems to be ignored which is good, but am I doing something wrong? Or is it really necessary to pay the (normally minimal) cost of constructing a response that won't be used?
Here's the some working sample code that demonstrates my attempt at message forwarding with typed actors:
#include <iostream>
#include "caf/all.hpp"
using namespace caf;
using namespace std;
using a_type = typed_actor<replies_to<int>::with<bool>>;
using b_type = typed_actor<replies_to<int>::with<bool>>;
actor worker()
{
return spawn(
[](event_based_actor *self) -> behavior
{
return
{
[self](int index)
{
aout(self) << "Worker: " << index << endl;
return index;
}
};
});
}
b_type::behavior_type bBehavior(b_type::pointer self)
{
return
{
[self](int value)
{
// Create blocking actor
scoped_actor blockingActor;
// Spawn pool workers and send each a message
auto pool = actor_pool::make(value, worker, actor_pool::round_robin());
for(int i = 0; i < value; ++i)
{
blockingActor->send(pool, i);
}
// Wait for completion
vector<int> results;
int i = 0;
blockingActor->receive_for(i, value) (
[&results](int value)
{
results.push_back(value);
});
blockingActor->send_exit(pool, exit_reason::user_shutdown);
self->quit();
return (value == results.size());
}
};
}
class A : public a_type::base
{
protected:
behavior_type make_behavior() override
{
return
{
[this](int value) -> bool
{
aout(this) << "Number of tasks: " << value << endl;
b_type forwardDestination = spawn(bBehavior);
auto castDestination = actor_cast<actor>(forwardDestination);
this->forward_current_message(castDestination);
this->quit();
return false;
}
};
}
};
void tester()
{
a_type testeeActor = spawn<A>();
scoped_actor self;
self->sync_send(testeeActor, 5).await(
[testeeActor, &self](bool success)
{
aout(self) << "All workers completed? " << (success ? "Yes!" : "No :(") << endl;
});
}
int main()
{
tester();
await_all_actors_done();
shutdown();
cout << "Press Enter to continue" << endl;
cin.get();
}
Therefore, I assume it's okay to call forward_current_message directly?
No, forward_current_message ist not part of the public API in CAF (and is thus not listed in Doxygen). This means the member function could be renamed, removed, or made protected/private at any time.
The best practice to forward messages to typed actors is delegate. This is a new feature (introduced with 0.14.1) and unfortunately is not mentioned in the manual yet. The best "documentation" currently available is its use in the unit test for typed actors.
The short version is: delegate is an alternative to send that forwards the responsibility for a request. In a typed actor, you can return delegated<T> instead of T from a message handler to indicate that an other actor will respond with a T to the original sender.
In your case, class A would be implemented like this:
class A : public a_type::base
{
protected:
behavior_type make_behavior() override {
return {
[this](int value) {
aout(this) << "Number of tasks: " << value << endl;
auto forwardDestination = spawn(bBehavior);
this->quit();
return delegate(forwardDestination, value);
}
};
}
};

reduce code duplication in c++

Can I reduce the following code to one function? the most part of them are the same. Thanks
void info(StreamLog &streamLog)
{
streamLog.ss << "info:";
streamLog.mFilter->setLogLevel("info");
}
void debug(StreamLog &streamLog)
{
streamLog.ss << "debug:";
streamLog.mFilter->setLogLevel("debug");
}
void warning(StreamLog &streamLog)
{
streamLog.ss << "warning:";
streamLog.mFilter->setLogLevel("warning");
}
void error(StreamLog &streamLog)
{
streamLog.ss << "error:";
streamLog.mFilter->setLogLevel("error");
}
void critical(StreamLog &streamLog)
{
streamLog.ss << "critical:";
streamLog.mFilter->setLogLevel("critical");
}
if you need more info,let me know
1st edited:
sorry ! I didnt explain my situation clearly. I use those function as manipulator. therefore, i can do
clog << info << ...
clog << warning<<...
I dont want to use
clog << log(info) <<...
any better way? thanks
void log(StreamLog &streamLog, const string& level)
{
streamLog.ss << level << ":";
streamLog.mFilter->setLogLevel(level);
}
Always try to see the common operation and abstract it in a different function.
I'm with #Rémi Benoit, though to provide you with an alternative, you can use an enum and a map:
enum log_level {
info, debug, warning, error, critical
}
void log(StreamLog& streamLog, log_level level) {
static const std::map<log_level, std::string> levels = {
{ info, "info" }, { debug, "debug" }, { warning, "warning" },
{ error, "error" }, { critical, "critical" }
};
auto iter = levels.find(level);
if(iter == levels.end()) return;
streamLog.ss << iter->second;
streamLog.mFilter->setLogLevel(iter->second);
}
The benefit of this is that your log level is limited only to what is in the enum (and the map), though if you do not require this constraint, it is better to use #Rémi's solution.
Here is an alternative to Mark's & Rémi's solutions using templates. This solution could be useful in areas where high performance is critical. The templates let the compiler bake in a lot of information into each templated function already so there should be a smaller run time cost especially when compared to the map lookup method.
Additionally this will constrain the values for the log_level being used at compile time rather than at run time.
enum class log_level { info, debug, warning, error, critical };
template<log_level> struct log_helper{ static const char* const text; };
template<> const char* const log_helper<log_level::info>::text = "info";
template<> const char* const log_helper<log_level::debug>::text = "debug";
template<> const char* const log_helper<log_level::warning>::text = "warning";
template<> const char* const log_helper<log_level::error>::text = "error";
template<> const char* const log_helper<log_level::critical>::text = "critical";
template<log_level level> void set_log_level(StreamLog& streamLog)
{
streamLog.ss<< log_helper<level>::text << ":";
streamLog.mFilter->setLogLevel(log_helper<level>::text);
}

Lazy transform in C++

I have the following Python snippet that I would like to reproduce using C++:
from itertools import count, imap
source = count(1)
pipe1 = imap(lambda x: 2 * x, source)
pipe2 = imap(lambda x: x + 1, pipe1)
sink = imap(lambda x: 3 * x, pipe2)
for i in sink:
print i
I've heard of Boost Phoenix, but I couldn't find an example of a lazy transform behaving in the same way as Python's imap.
Edit: to clarify my question, the idea is not only to apply functions in sequence using a for, but rather to be able to use algorithms like std::transform on infinite generators. The way the functions are composed (in a more functional language like dialect) is also important, as the next step is function composition.
Update: thanks bradgonesurfing, David Brown, and Xeo for the amazing answers! I chose Xeo's because it's the most concise and it gets me right where I wanted to be, but David's was very important into getting the concepts through. Also, bradgonesurfing's tipped Boost::Range :).
Employing Boost.Range:
int main(){
auto map = boost::adaptors::transformed; // shorten the name
auto sink = generate(1) | map([](int x){ return 2*x; })
| map([](int x){ return x+1; })
| map([](int x){ return 3*x; });
for(auto i : sink)
std::cout << i << "\n";
}
Live example including the generate function.
I think the most idiomatic way to do this in C++ is with iterators. Here is a basic iterator class that takes an iterator and applies a function to its result:
template<class Iterator, class Function>
class LazyIterMap
{
private:
Iterator i;
Function f;
public:
LazyIterMap(Iterator i, Function f) : i(i), f(f) {}
decltype(f(*i)) operator* () { return f(*i); }
void operator++ () { ++i; }
};
template<class Iterator, class Function>
LazyIterMap<Iterator, Function> makeLazyIterMap(Iterator i, Function f)
{
return LazyIterMap<Iterator, Function>(i, f);
}
This is just a basic example and is still incomplete as it has no way to check if you've reached the end of the iterable sequence.
Here's a recreation of your example python code (also defining a simple infinite counter class).
#include <iostream>
class Counter
{
public:
Counter (int start) : value(start) {}
int operator* () { return value; }
void operator++ () { ++value; }
private:
int value;
};
int main(int argc, char const *argv[])
{
Counter source(0);
auto pipe1 = makeLazyIterMap(source, [](int n) { return 2 * n; });
auto pipe2 = makeLazyIterMap(pipe1, [](int n) { return n + 1; });
auto sink = makeLazyIterMap(pipe2, [](int n) { return 3 * n; });
for (int i = 0; i < 10; ++i, ++sink)
{
std::cout << *sink << std::endl;
}
}
Apart from the class definitions (which are just reproducing what the python library functions do), the code is about as long as the python version.
I think the boost::rangex library is what you are looking for. It should work nicely with the new c++lambda syntax.
int pipe1(int val) {
return 2*val;
}
int pipe2(int val) {
return val+1;
}
int sink(int val) {
return val*3;
}
for(int i=0; i < SOME_MAX; ++i)
{
cout << sink(pipe2(pipe1(i))) << endl;
}
I know, it's not quite what you were expecting, but it certainly evaluates at the time you want it to, although not with an iterator iterface. A very related article is this:
Component programming in D
Edit 6/Nov/12:
An alternative, still sticking to bare C++, is to use function pointers and construct your own piping for the above functions (vector of function pointers from SO q: How can I store function pointer in vector?):
typedef std::vector<int (*)(int)> funcVec;
int runPipe(funcVec funcs, int sinkVal) {
int running = sinkVal;
for(funcVec::iterator it = funcs.begin(); it != funcs.end(); ++it) {
running = (*(*it))(running); // not sure of the braces and asterisks here
}
return running;
}
This is intended to run through all the functions in a vector of such and return the resulting value. Then you can:
funcVec funcs;
funcs.pushback(&pipe1);
funcs.pushback(&pipe2);
funcs.pushback(&sink);
for(int i=0; i < SOME_MAX; ++i)
{
cout << runPipe(funcs, i) << endl;
}
Of course you could also construct a wrapper for that via a struct (I would use a closure if C++ did them...):
struct pipeWork {
funcVec funcs;
int run(int i);
};
int pipeWork::run(int i) {
//... guts as runPipe, or keep it separate and call:
return runPipe(funcs, i);
}
// later...
pipeWork kitchen;
kitchen.funcs = someFuncs;
int (*foo) = &kitchen.run();
cout << foo(5) << endl;
Or something like that. Caveat: No idea what this will do if the pointers are passed between threads.
Extra caveat: If you want to do this with varying function interfaces, you will end up having to have a load of void *(void *)(void *) functions so that they can take whatever and emit whatever, or lots of templating to fix the kind of pipe you have. I suppose ideally you'd construct different kinds of pipe for different interfaces between functions, so that a | b | c works even when they are passing different types between them. But I'm going to guess that that's largely what the Boost stuff is doing.
Depending on the simplicity of the functions :
#define pipe1(x) 2*x
#define pipe2(x) pipe1(x)+1
#define sink(x) pipe2(x)*3
int j = 1
while( ++j > 0 )
{
std::cout << sink(j) << std::endl;
}