on Wed Mar 06 2013, Stefan Strasser
Hi everyone,
are there any guidelines for boost libraries on when to use traits classes and when to use a metafunction for each member of a would-be traits class?
Yeah, use metafunctions :-)
I've seen both in boost libraries. c++11 uses traits classes (iterator_traits, pointer traits, ...), but Abrahams and Gurtovoy [1] argue that traits classes ("traits blobs") should be avoided "at all costs" because they are an unnecessary concatenation of multiple metafunctions into one metafunction with multiple return values.
in practice, this means that if a single 'traits class member' aka metafunction result' ought to differ from the defaults, the entire traits class has to be reimplemented by the user, not only the metafunction whose result ought to differ from the default. on the other hand, asking a user to implement 5 or more metafunctions is much more tedious than implementing a traits class.
Not having the metafunctions decreases interoperability. Having the metafunctions and the traits classes increases confusion and clutter. It's already pretty darned tedious to produce specializations of another library's templates, because of the namespaces to open/close. I don't think asking users to provide a traits class offers much convenience advantage at all. There's usually a better way to get convenience, and that is to have the metafunctions' default implementations reach into one of their parameters for their results.
possibility 1)
template<...> struct property1 : mpl::bool_<...>{}; template<...> struct property2 : mpl::bool_<...>{}; template<...> struct property3 : mpl::bool_<...>{};
2)
template<...> struct ..._traits{ typedef mpl::bool_<...> property1; typedef mpl::bool_<...> property2; typedef mpl::bool_<...> property3; };
you could provide both, but in which "direction"? does a traits class refer to metafunctions, or do metafunctions refer to a traits class?
a)
template<...> struct ..._traits{ typedef typename property1<...>::type property1; typedef typename property2<...>::type property2; typedef typename property3<...>::type property3; };
b)
template<...> struct property1 : ..._traits<...>::property1{} template<...> struct property2 : ..._traits<...>::property2{} template<...> struct property3 : ..._traits<...>::property3{}
which is accessed when "calling" the metafunction?
If you think specializing metafunctions is painful, then clearly you need (b). But I don't think that.
it seems to me that metafunctions are better for the "caller" and traits classes are better for the implementor of the traits/metafunctions.
Only if the implementor has would have to implement all or most of the metafunctions, and then the convenience is only marginal, IMO.
in my case, the caller is a library and the implementor is the user, so you might choose a traits class over metafunctions. but what if a new member is added that wasn't there before (like "property4"). the implementor of an (old) traits class could not have known about the new member, so accessing the new member (by the library) will result in an error, while a new metafunction "property4" could simply return a default. should new properties of traits classes only be accessed after making sure they exist, using SFINAE? there isn't a precedent for this in c++11. only new traits classes were introduced, but no new members of already existing traits classes.
should metafunctions always be preferred?
IMO unless there's a compelling argument against them, yes.
why did c++11 choose traits classes over metafunctions, even though the concept of a metafunction was introduced in c++11 (
).
Because I got lazy and didn't try to educate all the contributors to the standard and control the design of the standard library in that way. P.S. Have you seen what they did with enable_if? -- Dave Abrahams