> Type erasure is the C++ way of achieving structural > subtyping. Given that C++ is a strongly typed language, > having structural subtyping is anything but fundamental - > in fact it is quite amazing that it works. > > As for the rest, blah, blah, blah. > > > > > > Anyway, I ran into the is_iterator trap a short time ago. > Consider this overload set: > > template <typename T> > data_holding_object<T> make_dho(); > > template <typename Iterator> > data_holding_object< > typename std::iterator_traits<Iterator>::value_type > > > make_dho(Iterator first, Iterator last); > > > Harmless, right? I want an empty dho, I create one using > make_dho<int>() (the actual situation in my library is > more complex; directly using the data_holding_object type > is impractical). I want a pre-filled one, I create one > using make_dho(v.begin(), v.end()). > > Well ... except that creating the overload set fails in > GCC 4. (Haven't tested other compilers yet.) > make_dho<int>() fails to compile. > I got quite creative trying to implement is_iterator. I > thought I had a solution that works using lazy_enable_if - > only to realize that the enable_if simply failed always, > not just for non-iterators. > > > > On the other hand, my type erasure in the same library > works beautifully. Being able to write source<int> instead > of a type that would fill 5 lines and have only a single > virtual call per operation as overhead is really nice.
You mean something like this?
#include <iostream> #include <list> using namespace std;
I think that the pattern you describe is similar to that used in boost::shared_ptr. And Scott Meyers uses this term to refer to that pattern in "My Most Important C++ Aha! Moments...Ever" (http://tinyurl.com/fr9yj).
Thanks a lot for the link! It seems to me that these guys really deserve the credit for having pioneered the idea: the paper is dated 1996. What's interesting is that the motivation that they give for their work is very much practical. Confirms my belief that good patterns always come from solving practical engineering problems. The abstract point of view, as in "OO vs. generic programming," is important, but it comes later. And it is useful only if it remains grounded in applied engineering.
As for the terminology, I do like their "External Polymorphism." There is one little catch, though: back then, in 1996, the default meaning of the word "polymorphism" was "runtime polymorphism." It seems to me that that has changed. Nowadays, I believe we would have to add the qualification "runtime." That would make it
One difference between the external polymorphism pattern of http://tinyurl.com/33lf7w and the more recent type erasure implementations along the lines of boost::any is this: the original external polymorphism pattern did not have the handle class on the outside. It was pretty much understood that you would work with pointers or references to the external base class. It seems to me that the idea of the handle class is a substantial addition to the pattern. Who deserves credit? Most likely Kevlin Henney, but I'm not sure.
> They have chosen not to use the term "type erasure" at > all, but that's not something that we should argue about. > Consensus on terminology is something that takes time to > evolve.
I don't mean that type erasure is the same as 'void*'. In fact, templates and object-oriented programming are orthogonal in C++. Templates work with objects as smoothly as with values. There is no "Trouble with Object-Oriented and Generic Programming" (maybe Boost has troubles, but that's their problem). Just in the tradition of STL most template aficionados ignore object-oriented programming in favor of value oriented programming.
Type erasure can occur at any stage of the executable's life span. Compile-time type erasure results in unnecessary casts and a performance hit because the executable doesn't know that there is a more specific structural subtype that can be guaranteed through static type-checking.
Run-time type erasure is actually quite different. It examines how an object reference is used in a particular context: a particular Operating Scope guarantees an operational level responsibility.
void* is what is known as an opaque data type, which itself is a loaded term that could mean many things. However, the intuitive definition of opaque data type in this context is "delayed type declaration". A delayed type declaration can result in Impostor Types.
Also, any rub-against involving object-orientedness and generic programming is strictly the result of unsafe type features. Eric Allen touches upon this very lightly in Chapter 7, "The Rogue Tile", of Bug Patterns in Java. A version of this chapter is in the IBM developerWorks archive.
@Thomas Becker @As for the terminology, I do like their "External Polymorphism." There is one little catch, though: back then, in 1996, the default meaning of the word "polymorphism" was "runtime polymorphism." It seems to me that that has changed. Nowadays, I believe we would have to add the qualification "runtime." That would make it "External Runtime Polymorphism"
Correct. Compile-time polymorphism is perfectly plausible and used to select at compile-time which function or data structure should be used, usually to reduce the overhead imposed by a vtable and calling a function by accessing it through the vtable.
I get the notion of "external" and "non-intrusive run-time", but these terms don't have suitable antonyms. What is internal polymorphism? What is intrusive run-time polymorphism? "External" and "non-intrusive run-time" both seem like frantic ontologies to describe something thoroughly analytical and inherent to object-oriented analysis.
Martin Fowler talks about a "Knowledge / Operational split" in his book Analysis Patterns, a concept which I have found to be very useful when figuring out what I want to say before figuring out how to say it. I googled "Knowledge / Operational split" (K/O split) a week ago but never got any results, so I am assuming it's not popular. In K/O Split, he breaks down responsibilities into knowledge-level and operational-level qualifications. I don't believe Martin Fowler ever intended for someone to use K/O split in a discussion on run-time polymorphism strategies, but it's an idea I'm passing along here.
>> How much work would it take to extend any_iterator to be able to iterate through e.g. a map's mapped_values? <<
One could answer that with "none at all" or with "that's not the point." If you need an iterator that does something specific, you need to create that iterator. (In this case, you'd use some suitable combination of an STL iterator with Boost iterator adaptors). Once you have a "concrete" iterator, you can assign it to suitable instantiations of the any_iterator class template. The point of the any_iterator is to allow uniform runtime treatment of different types of concrete iterators. Creating those concrete iterators in the first place is completely independent of the existence of the any_iterator.
> >> > How much work would it take to extend any_iterator to be > able to iterate through e.g. a map's mapped_values? > << > > One could answer that with "none at all" or with "that's > not the point."
Which could imply a studid question ... I probably browsed through the article too quick.
> Creating those concrete iterators in the first place is > completely independent of the existence of the > any_iterator.
So any_iterator could perhaps then be a valuable higher-level addition to the Boost.Iterator library instead?
> So any_iterator could perhaps then be a valuable > higher-level addition to the Boost.Iterator library
In principle, yes. At this point, however, it is not clear to me if there is enough interest in iterator type erasure. I personally believe very strongly in the usefulness of type erasure in modern C++ programming, especially as a way to reconcile generic and OO programming. I know that I'm not the only one, but I don't see widespread consensus on the issue.
> > So any_iterator could perhaps then be a valuable > > higher-level addition to the Boost.Iterator library > > In principle, yes. At this point, however, it is not clear > to me if there is enough interest in iterator type > erasure. I personally believe very strongly in the > usefulness of type erasure in modern C++ programming, > especially as a way to reconcile generic and OO > programming.
Right. I'm using a similar technique to be able to declare iterator support as part of e.g. abstract base class interfaces.
Thanks for pointing this out! It's that old glitch again that happens when showing C++ code in HTML: Despite the fact that the code is enclosed in <pre> </pre>, the angle brackets are not treated as literals. If you look at the page source, you'll see
That is displayed as just
To get the correct display, one must use < and > for the angle brackets.
The error is mine, I submitted it wrong. I'll email Frank Sommers and ask him to fix it.