|
View:
New views
3 Messages
—
Rating Filter:
Alert me
|
|
|
|
|
|
Re: glas Digest, Vol 25, Issue 1Hi Robert,
Thanks for the suggestion. I already had this in mind for the traits classes, but I could consider this too for the free functions indeed. However, 'default' function implementations are not (yet) allowed in Concept C++, so, therefore I would only do this for typedefs (i.e. traits). Karl Robert P. Goddard wrote: >Karl, > >I'm generally supportive of your idea of using free functions and traits >in place of member functions and member typedefs. However, I caution >against doing away with the latter. The Standard Library provides a good >example (as it often does): The default definition for >std::iterator_traits is, in part, > >template <class Iterator> >struct iterator_traits { > typedef typename Iterator::value_type value_type; >}; > >That way the developer of an iterator has a choice: Provide a >specialization of iterator_traits, or provide a nested typedef and let >the default iterator_traits pick it up. The latter is often easier, in >part because nested typedefs can come from a base class. The former is >sometimes necessary, such as non-class types or classes developed by >someone else. > >The same principle applies to free functions versus member functions: A >reasonable default definition of size() could be > >template <class V> >VectorExpression<V>::size_type size( const V& size ) >{ > return V.size(); >} > >Again, the developer of a new VectorExpression class has a choice: >Overload the free size() function or provide a member function. The >latter is often preferred, e.g. when it's a virtual function, and the >former is sometimes necessary, e.g. when it's a non-class or you don't >own it. > >In both cases, the free version is the preferred interface for users. >The member version is provided primarily for developers, who can be the >same users wearing different hats. > > > [Karl.Meerbergen.vcf] begin:vcard fn:Karl Meerbergen n:Meerbergen;Karl org:K.U. Leuven;Department of Computer Science adr:;;Celestijnenlaan 200A;Heverlee (Leuven);;B-3001;Belgium email;internet:Karl.Meerbergen@... tel;work:+32 16 32 79 59 tel;fax:+32 16 32 79 96 tel;cell:+32 474 26 66 59 url:http:://www.cs.kuleuven.be/~karlm version:2.1 end:vcard _______________________________________________ glas mailing list glas@... http://lists.boost.org/mailman/listinfo.cgi/glas |
|
|
Re: glas Digest, Vol 25, Issue 1Hello Robert, Karl,
On 05/04/07, Robert P. Goddard <Robert_Goddard@...> wrote: > Karl, > > I'm generally supportive of your idea of using free functions and traits > in place of member functions and member typedefs. However, I caution > against doing away with the latter. The Standard Library provides a good > example (as it often does): <snip> It is interesting that you refer to the STL for this. I was just reading the document "Notes on Programming", by Alexander Stepanov ( http://www.stepanovpapers.com/notes.pdf ). Stepanov is the designer and original implementer of the Standard Template Library, and on page 21 he writes <quote> While we could make a member function to return length, it is better to make it a global friend function. If we do that, we will be able eventually to define the same function to work on built-in arrays and achieve greater uniformity of design. I made size into a member function in STL in an attempt to please the standard committee. I knew that begin, end and size should be global functions but was not willing to risk another fight with the committee. In general, there were many compromises of which I am ashamed. It would have been harder to succeed without making them, but I still get a metallic taste in my mouth when I encounter all the things that I did wrong while knowing full how to do them right. Success, after all, is much overrated. I will be pointing to the incorrect designs in STL here and there: some were done because of political considerations, but many were mistakes caused by my inability to discern general principles.) </quote> Given the feelings of the original STL-designer on this matter it seems better to me not to take it as an example (except when you want to achieve the same look & feel as STL). David Abrahams and Aleksey Gurtovoy make a similar point in their book "C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond" about the usage of traits in the STL: <quote> The traits templates in the standard library all follow the "multiple return values" model. We refer to this kind of traits template as a "blob," because it's as though a handful of separate and loosely related metafunctions were mashed together into a single unit. We will avoid this idiom at all costs, because it creates major problems. First of all, there's an efficiency issue: The first time we reach inside the iterator_traits for its ::value_type, the template will be instantiated. That means a lot of things to the compiler, but to us the important thing is that at that point the compiler has to work out the meaning of every declaration in the template body that happens to depend on a template parameter. In the case of iterator_traits, that means computing not only the value_type, but the four other associated types as welleven if we're not going to use them. The cost of these extra type computations can really add up as a program grows, slowing down the compilation cycle. Remember that we said type computations would get much more interesting? "More interesting" also means more work for your compiler, and more time for you to drum your fingers on the desk waiting to see your program work. Second, and more importantly, "the blob" interferes with our ability to write metafunctions that take other metafunctions as arguments. To wrap your mind around that, consider a trivial runtime function that accepts two function arguments: template <class X, class UnaryOp1, class UnaryOp2> X apply_fg(X x, UnaryOp1 f, UnaryOp2 g) { return f(g(x)); } That's not the only way we could design apply_fg, though. Suppose we collapsed f and g into a single argument called blob, as follows: template <class X, class Blob> X apply_fg(X x, Blob blob) { return blob.f(blob.g(x)); } The protocol used to call f and g here is analogous to the way you access a "traits blob": to get a result of the "function," you reach in and access one of its members. The problem is that there's no single way to get at the result of invoking one of these blobs. Every function like apply_fg will use its own set of member function names, and in order to pass f or g on to another such function we might need to repackage it in a wrapper with new names. "The blob" is an anti-pattern (an idiom to be avoided), because it decreases a program's overall interoperability, or the ability of its components to work smoothly together. The original choice to write apply_fg so that it accepts function arguments is a good one, because it increases interoperability. When the callable arguments to apply_fg use a single protocol, we can easily exchange them: #include <functional> float log2(float); int a = apply_fg(5.Of, std::negate<float>(), log2); int b = apply_fg(3.14f, log2, std::negate<float>()); The property that allows different argument types to be used interchangeably is called polymorphism; literally, "the ability to take multiple forms." To achieve polymorphism among metafunctions, we'll need a single way to invoke them. The convention used by the Boost libraries is as follows: metafunction-name<type-arguments...>::type >From now on, when we use the term metafunction, we'll be referring to templates that can be "invoked" with this syntax. </quote> Regards, Bert _______________________________________________ glas mailing list glas@... http://lists.boost.org/mailman/listinfo.cgi/glas |
| Free embeddable forum powered by Nabble | Forum Help |