[serialization] Proposal: make 'version' trait (and others) enable_if-friendly

I have recently run up against a limitation of the serialization library in terms of specializing the 'version' trait. Fortunately, I believe the issue has a simple workaround which I don't believe will break existing code. In general, one can change the version of a serialized class by specializing the 'version' template class: ============================== template <typename T> struct version { BOOST_STATIC_CONSTANT(unsigned int, value = 0); }; ... template <> struct version<MyType> { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ============================== However, this approach does not work if 'MyType' is of the form (see [1]): ============================== template <typename T> struct Foo { struct MyType { int data1; }; }; template <typename T> struct version<Foo<T>::MyType> // ERROR { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ============================== It's not possible for the compiler to deduce the enclosing type, so this doesn't work. However, if MyType can be identified by a metafunction, then I can use enable_if to select the specialization of 'version' that I want. However, this requires support from 'version' itself: ============================== template <typename T, typename Enable = void> // NOTE new template parameter struct version { BOOST_STATIC_CONSTANT(unsigned int, value = 0); }; ... template <typaneme T> struct version<MyType, typename boost::enable_if<is_mytype<T> >::type> { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ============================== So, my proposal is simply to add this extra template parameter to 'version'. I do not believe other uses of 'version' will be affected, but it would not surprise me if I've overlooked something. Presumably the same modification could be made to implementation_level etc. A patch (for 'version' only) against trunk is enclosed below. Comments are welcome! Thanks, -Gabe [1] http://boost.2283326.n4.nabble.com/serialization-versioning-for-class-nested... =================================================================== --- version.hpp (revision 81723) +++ version.hpp (working copy) @@ -32,7 +32,7 @@ // default version number is 0. Override with higher version // when class definition changes. -template<class T> +template<class T, class Enable=void> struct version { template<class U> @@ -53,8 +53,8 @@ }; #ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION -template<class T> -const int version<T>::value; +template<class T, class Enable> +const int version<T, Enable>::value; #endif } // namespace serialization ===================================================================

On Wed, Dec 5, 2012 at 12:42 PM, Gabriel Redner <gredner@gmail.com> wrote:
I have recently run up against a limitation of the serialization library in terms of specializing the 'version' trait. Fortunately, I believe the issue has a simple workaround which I don't believe will break existing code.
In general, one can change the version of a serialized class by specializing the 'version' template class: ============================== template <typename T> struct version { BOOST_STATIC_CONSTANT(unsigned int, value = 0); }; ... template <> struct version<MyType> { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ==============================
However, this approach does not work if 'MyType' is of the form (see [1]): ============================== template <typename T> struct Foo { struct MyType { int data1; }; };
template <typename T> struct version<Foo<T>::MyType> // ERROR { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ============================== It's not possible for the compiler to deduce the enclosing type, so this doesn't work.
However, if MyType can be identified by a metafunction, then I can use enable_if to select the specialization of 'version' that I want. However, this requires support from 'version' itself: ============================== template <typename T, typename Enable = void> // NOTE new template parameter struct version { BOOST_STATIC_CONSTANT(unsigned int, value = 0); }; ... template <typaneme T> struct version<MyType, typename boost::enable_if<is_mytype<T> >::type> { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ==============================
So, my proposal is simply to add this extra template parameter to 'version'. I do not believe other uses of 'version' will be affected, but it would not surprise me if I've overlooked something. Presumably the same modification could be made to implementation_level etc.
A patch (for 'version' only) against trunk is enclosed below. Comments are welcome!
Sounds reasonable to me.
Thanks, -Gabe
[1] http://boost.2283326.n4.nabble.com/serialization-versioning-for-class-nested...
=================================================================== --- version.hpp (revision 81723) +++ version.hpp (working copy) @@ -32,7 +32,7 @@
// default version number is 0. Override with higher version // when class definition changes. -template<class T> +template<class T, class Enable=void> struct version { template<class U> @@ -53,8 +53,8 @@ };
#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION -template<class T> -const int version<T>::value; +template<class T, class Enable> +const int version<T, Enable>::value; #endif
} // namespace serialization ===================================================================
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
- Jeff

Gabriel Redner wrote:
I have recently run up against a limitation of the serialization library in terms of specializing the 'version' trait. Fortunately, I believe the issue has a simple workaround which I don't believe will break existing code.
In general, one can change the version of a serialized class by specializing the 'version' template class: ============================== template <typename T> struct version { BOOST_STATIC_CONSTANT(unsigned int, value = 0); }; ... template <> struct version<MyType> { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ==============================
However, this approach does not work if 'MyType' is of the form (see [1]): ============================== template <typename T> struct Foo { struct MyType { int data1; }; };
template <typename T> struct version<Foo<T>::MyType> // ERROR { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ============================== It's not possible for the compiler to deduce the enclosing type, so this doesn't work.
Hmm - doesn't the fillowing work? - for each T used template <> struct version<Foo<t>::MyType>{ BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; I understand it might be a little more tedious and not that clever but it's much, much, much less time consuming to understand, documment, explain and support. Your suggestion is clever - but I just don't think it's cost effective. Robert Ramey

On Thu, Dec 6, 2012 at 10:03 AM, Robert Ramey <ramey@rrsd.com> wrote:
Gabriel Redner wrote:
I have recently run up against a limitation of the serialization library in terms of specializing the 'version' trait. Fortunately, I believe the issue has a simple workaround which I don't believe will break existing code.
In general, one can change the version of a serialized class by specializing the 'version' template class: ============================== template <typename T> struct version { BOOST_STATIC_CONSTANT(unsigned int, value = 0); }; ... template <> struct version<MyType> { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ==============================
However, this approach does not work if 'MyType' is of the form (see [1]): ============================== template <typename T> struct Foo { struct MyType { int data1; }; };
template <typename T> struct version<Foo<T>::MyType> // ERROR { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ============================== It's not possible for the compiler to deduce the enclosing type, so this doesn't work.
Hmm - doesn't the fillowing work? - for each T used
template <> struct version<Foo<t>::MyType>{ BOOST_STATIC_CONSTANT(unsigned int, value = 1); };
I understand it might be a little more tedious and not that clever but it's much, much, much less time consuming to understand, documment, explain and support. Your suggestion is clever - but I just don't think it's cost effective.
FWIW, the similar approach (with an additional template parameter for SFINAE-based dispatch) is used in Boost.Phoenix, Boost.Proto and probably elsewhere and is described here: http://www.boost.org/doc/libs/release/doc/html/proto/appendices.html#boost_p... With the right use (as described at the linked page) I expect it to be nearly as fast as the regular specialization.

On Wed, Dec 5, 2012 at 10:20 PM, Andrey Semashev <andrey.semashev@gmail.com>wrote:
On Thu, Dec 6, 2012 at 10:03 AM, Robert Ramey <ramey@rrsd.com> wrote:
Gabriel Redner wrote:
I have recently run up against a limitation of the serialization library in terms of specializing the 'version' trait. Fortunately, I believe the issue has a simple workaround which I don't believe will break existing code.
In general, one can change the version of a serialized class by specializing the 'version' template class: ============================== template <typename T> struct version { BOOST_STATIC_CONSTANT(unsigned int, value = 0); }; ... template <> struct version<MyType> { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ==============================
However, this approach does not work if 'MyType' is of the form (see [1]): ============================== template <typename T> struct Foo { struct MyType { int data1; }; };
template <typename T> struct version<Foo<T>::MyType> // ERROR { BOOST_STATIC_CONSTANT(unsigned int, value = 1); }; ============================== It's not possible for the compiler to deduce the enclosing type, so this doesn't work.
Hmm - doesn't the fillowing work? - for each T used
template <> struct version<Foo<t>::MyType>{ BOOST_STATIC_CONSTANT(unsigned int, value = 1); };
Is it possible you won't know all T's at the point one wishes to define this? But, regardless...
I understand it might be a little more tedious and not that clever
but it's much, much, much less time consuming to understand, documment, explain and support. Your suggestion is clever - but I just don't think it's cost effective.
You exaggerate :) Besides... FWIW, the similar approach (with an additional template parameter for
SFINAE-based dispatch) is used in Boost.Phoenix, Boost.Proto and probably elsewhere and is described here:
http://www.boost.org/doc/libs/release/doc/html/proto/appendices.html#boost_p...
With the right use (as described at the linked page) I expect it to be nearly as fast as the regular specialization.
...it's more or less an established idiom, at least within Boost. - Jeff

Hi Robert, Thanks for taking a look, and for developing the library!
Hmm - doesn't the fillowing work? - for each T used
template <> struct version<Foo<t>::MyType>{ BOOST_STATIC_CONSTANT(unsigned int, value = 1); };
I understand it might be a little more tedious and not that clever but it's much, much, much less time consuming to understand, documment, explain and support. Your suggestion is clever - but I just don't think it's cost effective.
From the library's point of view, this is a change that increases flexibility while leaving existing use-cases untouched. Since this
Technically - yes, I could probably enumerate all types. However, my example is severely simplified and in reality the 'Foo'-equivalent has several template parameters. Enumerating all of the combinations would create its own maintenance nightmare and essentially defeat the benefits of templating. I don't mean to dismiss your suggestion, but I think you are looking at things from the wrong perspective. Your objection seems not to be with the proposed library change, but with how I am going to use it. pattern is common in other boost libraries, I would say there is a clear need for it as well as precedent. If a user doesn't need the added flexibility, they can ignore the extra parameter. But, if a user *does* need it, it's a lifesaver. Thanks, -Gabe

Gabriel Redner wrote:
Hi Robert,
Thanks for taking a look, and for developing the library!
Hmm - doesn't the fillowing work? - for each T used
template <> struct version<Foo<t>::MyType>{ BOOST_STATIC_CONSTANT(unsigned int, value = 1); };
I understand it might be a little more tedious and not that clever but it's much, much, much less time consuming to understand, documment, explain and support. Your suggestion is clever - but I just don't think it's cost effective.
Technically - yes, I could probably enumerate all types. However, my example is severely simplified and in reality the 'Foo'-equivalent has several template parameters. Enumerating all of the combinations would create its own maintenance nightmare and essentially defeat the benefits of templating.
I don't mean to dismiss your suggestion, but I think you are looking at things from the wrong perspective. Your objection seems not to be with the proposed library change, but with how I am going to use it.
It's not so much that, its with the work I have to do to implement it, address compiler dependent issues, document it, explain it to people who ask about it (it's not obvious), etc.
From the library's point of view, this is a change that increases flexibility while leaving existing use-cases untouched. Since this pattern is common in other boost libraries, I would say there is a clear need for it as well as precedent. If a user doesn't need the added flexibility, they can ignore the extra parameter. But, if a user *does* need it, it's a lifesaver.
There's nothing that prevents you from implementing your own change. Actually I'm still trying to wrap my head around why template<Foo<T>::MyType > doesn't work. Robert Ramey
Thanks, -Gabe
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Thu, Dec 6, 2012 at 1:22 PM, Robert Ramey <ramey@rrsd.com> wrote:
Gabriel Redner wrote:
Hi Robert,
Thanks for taking a look, and for developing the library!
Hmm - doesn't the fillowing work? - for each T used
template <> struct version<Foo<t>::MyType>{ BOOST_STATIC_CONSTANT(unsigned int, value = 1); };
I understand it might be a little more tedious and not that clever but it's much, much, much less time consuming to understand, documment, explain and support. Your suggestion is clever - but I just don't think it's cost effective.
Technically - yes, I could probably enumerate all types. However, my example is severely simplified and in reality the 'Foo'-equivalent has several template parameters. Enumerating all of the combinations would create its own maintenance nightmare and essentially defeat the benefits of templating.
I don't mean to dismiss your suggestion, but I think you are looking at things from the wrong perspective. Your objection seems not to be with the proposed library change, but with how I am going to use it.
It's not so much that, its with the work I have to do to implement it,
I.e., patches welcome.
address compiler dependent issues,
None AFAIK (this technique is already used in Boost).
document it,
Optional for the moment, I'd say.
explain it to people who ask about it (it's not obvious), etc.
Easy way is to refer them to Boost.Proto docs et al. (I forget the rest of the libraries that do this).
From the library's point of view, this is a change that increases flexibility while leaving existing use-cases untouched. Since this pattern is common in other boost libraries, I would say there is a clear need for it as well as precedent. If a user doesn't need the added flexibility, they can ignore the extra parameter. But, if a user *does* need it, it's a lifesaver.
There's nothing that prevents you from implementing your own change.
Actually I'm still trying to wrap my head around why
template<Foo<T>::MyType > doesn't work.
T is not in a deduced context, i.e., the compiler cannot deduce what T is. You can try it yourself if you don't believe me :) - Jeff

explain it to people who ask about it (it's not obvious), etc.
Easy way is to refer them to Boost.Proto docs et al. (I forget the rest of the libraries that do this).
Actually, this technique is documented in the Boost.EnableIf library, too [1] (see section 3.2). That is probably the canonical place to refer people to. Regards, Nate [1] http://www.boost.org/doc/libs/1_52_0/libs/utility/enable_if.html

Jeffrey Lee Hellrung, Jr. wrote:
T is not in a deduced context, i.e., the compiler cannot deduce what T is. You can try it yourself if you don't believe me :)
I confess I still don't get it. Seems to me to be a special instance of partial template specialization. Anyway The following example works on both MSVC 9.0 and GCC 4.5.3 template<class T> struct trait { }; template<> struct trait<int> { }; template<class T> struct outer { struct inner { }; }; template<class T> struct indirect : public outer<T>::inner { }; template<class U> struct trait< indirect<U> > { }; without the indirect it did indeed fail on both compilers. I would seem to me that a variation on the above above could be adapted to give the original poster

On Thu, Dec 6, 2012 at 3:13 PM, Robert Ramey <ramey@rrsd.com> wrote:
Jeffrey Lee Hellrung, Jr. wrote:
T is not in a deduced context, i.e., the compiler cannot deduce what T is. You can try it yourself if you don't believe me :)
I confess I still don't get it. Seems to me to be a special instance of partial template specialization.
Nathan gave a very nice explanation for why it doesn't work. Anyway
The following example works on both MSVC 9.0 and GCC 4.5.3
template<class T> struct trait { };
template<> struct trait<int> { };
template<class T> struct outer { struct inner { }; };
template<class T> struct indirect : public outer<T>::inner { };
template<class U> struct trait< indirect<U> > { };
without the indirect it did indeed fail on both compilers.
I would seem to me that a variation on the above above could be adapted to give the original poster
I don't see how this helps specializing trait< outer<T>::inner >. How would you arrange to always invoke the trait on this derived indirect type? Where does indirect come from, anyway? The user, the provider of the trait, someone else? In any case, the fact that this technique is used in several other Boost libraries is a hint that there is no generally applicable workaround, and even case-specific workarounds (such as something like above) would almost surely be more onerous than just adding a defaulted Enable type template parameter to the trait for the sole purpose of enabling SFINAE to address this use case. - Jeff

Jeffrey Lee Hellrung, Jr. wrote: In any case, the fact that this technique is used in several other Boost libraries is a hint that there is no generally applicable workaround, and even case-specific workarounds (such as something like above) would almost surely be more onerous than just adding a defaulted Enable type template parameter to the trait for the sole purpose of enabling SFINAE to address this use case.
Jeff said better than I could. Focusing on finding a workaround for my specific use-case is beside the point. The proposed change permits the whole weight of metaprogramming to be brought to bear in a natural and idiomatic way, without the need to hunt around in the language for case-specific fixes. -Gabe

Actually I'm still trying to wrap my head around why
template<Foo<T>::MyType > doesn't work.
Imagine you had a main template: template <typename T> struct Bar { ... }; and suppose a partial specialization like this were allowed: template <typename T> struct Bar<Foo<T>::MyType> { ... }; Now suppose someone instantiates Bar<int>. The compiler has to figure out whether to use the main template or the partial specialization. How does the compiler do that? In some special cases, it can be figured out (for example, if Foo<T>::MyType is defined as T). However, in the general case, Foo<T>::MyType can be the result of the some complicated metafunction that depends on T. It is intractable for the compiler to figure out what inputs to that metafunction (i.e., what values of T), yield a result of 'int'. It is for the same reason that you can't do this in a template function: template <typename T> void bar(Foo<T>::MyType foo); and expect that T will be deduced (it works if T is provided explicitly). Regards, Nate
participants (5)
-
Andrey Semashev
-
Gabriel Redner
-
Jeffrey Lee Hellrung, Jr.
-
Nathan Ridge
-
Robert Ramey