Boost logo

Boost :

Subject: [boost] An alternative approach to TypeErasure
From: Pyry Jahkola (pyry.jahkola_at_[hidden])
Date: 2012-06-24 06:34:13


Hi all,

I was quite impressed by Sean Parent's Friday talk in C++Now! 2012 (see
slides in [1], code in [2]) and was left wondering whether the
generation of his polymorphic object wrapper could be made simple
somehow in C++11.

[1]:
https://github.com/boostcon/cppnow_presentations_2012/raw/master/fri/value_semantics/value_semantics.pdf

[2]:
https://github.com/boostcon/cppnow_presentations_2012/blob/master/fri/value_semantics/value_semantics.cpp

What I ended up with is tricking ADL into allowing the overloading of
arbitrary (specifically crafted) function objects that I call
_callables_, and then letting the user define interfaces by specifying
sets of ("member") function signatures with a callable. You can find
the code, and my rambling introductory README file, in GitHub:

    https://github.com/pyrtsa/poly

Comments are welcome!

* * *

Here's a somewhat shorter intro:

1) Create a callable F by simply deriving it from poly::callable<F>:

    struct draw_ : poly::callable<draw_> {};
    constexpr draw_ draw = {};

2) Or use the convenience macro POLY_CALLABLE(name) to do the same:

    POLY_CALLABLE(to_string); // type `to_string_` and object `to_string`

3) Overload your callables by overloading the function named `call`,
with the callable as first argument:

    template <typename T>
    void call(draw_, T const & x, std::ostream & out, std::size_t position) {
        out << std::string(position, ' ') << x << std::endl;
    }

    std::string call(to_string_, int i) { return std::to_string(i); }

    template <typename T>
    std::string call(to_string_, std::vector<T> const & xs) {
        std::ostringstream s;
        s << '[';
        auto i = begin(xs), e = end(xs);
        if (i != e) s << *i++;
        while (i != e) s << ", " << *i++;
        s << ']';
        return s.str();
    }

Now you can call ::draw(x, o, p) and ::to_string(x) for several
different types of x:

    ::draw(123, std::cout, 2); // prints " 123" to stdout
    ::to_string(std::vector<int>(1, 2, 3)); // "[1, 2, 3]"

But you can likewise extend ::draw and ::to_string further by
overloading them for your own types too.

4) Define an interface by specifying the set of function signatures as
template arguments to poly::interface:

    using drawable = poly::interface<
        void(draw_, poly::self const &, std::ostream &, std::size_t,
        std::string(to_string_, poly::self const &)>;

Now, the following calls got automatically defined, and forwarded to
whatever type you successfully construct a `drawable` from:

    void call(draw_, drawable const &, std::ostream &, std::size_t);
    std::string call(to_string_, drawable const &);

* * *

I am aware that Steven Watanabe has put some astonishing work on his
proposed Boost.TypeErasure. I'm open for discussion whether and how his
or mine is a better approach, or if we could combine these into an
eventual Boost.Interface library or such!

* * *

PS. Like I mention in my GitHub page, this kind of type erasure is
common in functional languages like Haskell or Clojure. The problem
they are solving there is known as the Expression Problem. IMO it makes
very much sense to have the corresponding construct in C++ too, as
we're moving towards a more and more functional style with parallelism
and such.

Best Regards,

-- 
Pyry Jahkola
pyry.jahkola_at_[hidden]
https://twitter.com/pyrtsa

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk