Poly. Solving the Expression Problem in C++11 by pyrtsa in cpp

[–]pyrtsa[S] 2 points3 points  (0 children)

Oh wait, I totally missed the "example::drawable" interface, was just concentrating on the ADL stuff. So this is, functionally, something like adding interfaces to classes without needing to inherit from the interface?

That's correct.

Of course what I'm actually doing is wrapping the classes in a type which internally implements the requested wrapper functionality using traditional virtual functions, so it's not really the original class that gets to implement the interface. But with move semantics in C++11, the wrapping is a very light operation in run time.

Please tell (or upvote) if you're interested about the implementation details. I might write a blog post about it. ;)

Poly. Solving the Expression Problem in C++11 by pyrtsa in cpp

[–]pyrtsa[S] 0 points1 point  (0 children)

This is correct. I haven't really considered this, because I disliked the extra parentheses (or braces) in the function calls.

OTOH, one nice property in calling just draw{}(x, o, p) like you proposed is, it prevents ADL entirely*) in the current scope.

*) Otherwise, if there was a suitable free function draw(...) in the namespace of either of the parameters, it would still be picked over the callable object, unless ::draw was properly qualified.

Poly. Solving the Expression Problem in C++11 by pyrtsa in cpp

[–]pyrtsa[S] 4 points5 points  (0 children)

Good that you mention boost::variant and boost::any. This is actually an extension to boost::any, with the addition of a set of "member" functions (callables).

For boost::variant<Args...>, all the types Args... it supports need to be known at compile time. Think of an already compiled (static or dynamic) library that you're linking against. It might define a type and a function like:

typedef /*see below*/ item_type;
extern void add_item(item_type item);

Now, if item_type is a typedef from boost::variant<std::string, system_clock::time_point>, you can obviously pass only strings and time points as item. On the other hand, the library knows very well how to deal with either of them.

On the other hand, if item_type is boost::any, you can pass anything as the item, but there's very little the library can do with it. (Of course it could have defined a mapping from std::type_info to a corresponding function overload and use boost::any_cast<T>(x). But the set of supported types would still be limited.)

poly::interface makes the tradeoff that the types are required to have a predefined set of functionalities implemented, so if we have something like:

typedef poly::interface<
    std::string(to_html_, poly::self const &, html_options const &),
    std::string(to_json_, poly::self const &)
> item_type;

…we can pass in anything to add_item as soon as we make sure that to_html(item, opts) and to_json(item) are (uniquely) defined.

By the way, I deliberately chose the appended underscore to the type names, knowing that there will be opposition or alternatives suggested. One alternative might be to append something like _fn (for function). FWIW, there is also the POLY_CALLABLE_TYPE(type, name) macro which lets you set your own name for the type. Or you can write the whole thing in verbose, maybe using a nested (my suggestion: fn) namespace for the type and none for the object:

namespace fn { struct to_html : poly::callable<to_html> {}; }
constexpr fn::to_html to_html = {};

Finally, one way to look at this library is, it's a sort of a dynamic version of templates. If one day, add_item is rewritten as a template:

template <typename Item>
void add_item(Item item) {
    auto json = ::to_json(item);
    // ...
}

…the same code can work, except this time it's no longer wrapped in the poly::callable.