[variant] wish list

Hi Eric! It's good to see that you've got back to the list. For a long time now, there was no one to answer users' questions and comments, So I'm taking this (rare :-) ) opportunity to present my wish list for Boost.Variant. First of all, I'd like to say that I think it's a great library. I've become addicted to it... However, there are some things I think are missing. 1. add operator != There's operator==, and adding operator!= would be both easy and useful. 2. apply_visitor should accept the visitor by value std algorithms (such as for_each) accept their functors by value, not by [const] reference. I think that visitors should be considered as variant's equivalent to std functors, and thus be passed by value. This will allow me to pass temporary visitor objects, which is a very common use case, just as with std functors. It will also allow you to delete the two versions of apply_visitor (one taking a reference, one taking a const reference), and leave only one. 3. add support for non-copyable types, a-la Boost.Optional's in-place factory I found myself needing a variant of non-copyable types, and had to resort to dynamic allocation to solve it. Luckily for me, it wasn't a performance-critical code, so I could live with it. But of course it's not optimal... 4. add more flavours of get() Currently the get<T>() function has the semantics of "return current value only if it is of type implicitly convertible to T". But there are other usages, even more useful than this (and if there are other get()s, the name of the original get() should be changed to, for example, get_implicit). One of them is "return current value only if it is exactly of type T (using type_info)". The function will not compile if T is not any of the variant's types. A possible name for it could be get_exact(), but of course there might be better names. Another usage I found useful is when all of the variant's types are implicitly convertible to a common T (e.g, they all inherit from T). This function will not compile if any of the variant's types can't be implicitly convertible to T, but if it does compile, then it ensures it can't fail in run-time (in contrast to get_implicit and get_exact, which can fail in run-time). A possible name is get_common(). I've attached a file containing my implementation of get_exact and get_common, in case you're interested. All the preprocessor magic there is beyond my understanding, I just copied it from your code, and hope I didn't make any mistakes... 5. support for reference types I don't know if Boost.Variant currently supports reference types, nor am I certain it should, but it's a possibility... There was a debate about Boost.Optional's support for reference types, which reached no clear conclusion. Maybe you're familiar with the details, but if not, in short, the problem is with code such as int a = ...; int b = ...; optional<int &> x; // x in initialized to 'empty' x = a; // x is binded to a's reference x = b; // is x rebinded to b's reference, or is a assigned the value in b? The reason I bring it up here, is because I think it's relevant to Boost.Variant as well. Variant of reference types will raise the exact same questions, which should probably get the exact same answers. IOW, both the question of whether to support reference types, and the question of how to support them, should apply to both Boost.Optional and Boost.Variant uniformly. It's an open issue. 6. propose it for TR2 For the benefit of all of us... Thanks again for a great library, Yuval #ifndef VariantGet_h #define VariantGet_h #include <Utils/Variant.h> #include <boost/mpl/assert.hpp> #include <boost/mpl/contains.hpp> namespace boost { template <typename T> struct get_common_visitor : public static_visitor<T> { T operator()(T t) { return t; } }; template <typename T, typename Variant> typename add_reference<T>::type get_common(Variant &v) { get_common_visitor<typename add_reference<T>::type> visitor; return v.apply_visitor(visitor); } template <typename T, typename Variant> typename add_reference<const T>::type get_common(const Variant &v) { get_common_visitor<typename add_reference<const T>::type> visitor; return v.apply_visitor(visitor); } template <typename T, typename Variant> void assert_variant_has_type(const Variant &) { BOOST_MPL_ASSERT((mpl::contains<typename Variant::types, T>)); } template <typename T> struct get_exact_visitor : public static_visitor<T> { T operator()(T t) { return t; } }; template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_pointer<U>::type get_exact( boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >* operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { assert_variant_has_type<U>(*operand); typedef typename add_pointer<U>::type U_ptr; if (!operand) return static_cast<U_ptr>(0); if (operand->type() != typeid(U)) return static_cast<U_ptr>(0); detail::variant::get_visitor<U> v; return operand->apply_visitor(v); } template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_pointer<const U>::type get_exact( const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >* operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { assert_variant_has_type<U>(*operand); typedef typename add_pointer<const U>::type U_ptr; if (!operand) return static_cast<U_ptr>(0); if (operand->type() != typeid(U)) return static_cast<U_ptr>(0); detail::variant::get_visitor<const U> v; return operand->apply_visitor(v); } template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_reference<U>::type get_exact( boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { typedef typename add_pointer<U>::type U_ptr; U_ptr result = get_exact<U>(&operand); if (!result) throw bad_get(); return *result; } template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_reference<const U>::type get_exact( const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { typedef typename add_pointer<const U>::type U_ptr; U_ptr result = get_exact<const U>(&operand); if (!result) throw bad_get(); return *result; } } // namespace #endif /* ! defined VariantGet_h */

Hi, I'd also like to see apply_visitor with more arguments. Is there a reason it is limited to 2? Chris On 6/5/06, Yuval Ronen <ronen_yuval@yahoo.com> wrote:
Hi Eric! It's good to see that you've got back to the list. For a long time now, there was no one to answer users' questions and comments, So I'm taking this (rare :-) ) opportunity to present my wish list for Boost.Variant.
First of all, I'd like to say that I think it's a great library. I've become addicted to it... However, there are some things I think are missing.
1. add operator !=
There's operator==, and adding operator!= would be both easy and useful.
2. apply_visitor should accept the visitor by value
std algorithms (such as for_each) accept their functors by value, not by [const] reference. I think that visitors should be considered as variant's equivalent to std functors, and thus be passed by value. This will allow me to pass temporary visitor objects, which is a very common use case, just as with std functors. It will also allow you to delete the two versions of apply_visitor (one taking a reference, one taking a const reference), and leave only one.
3. add support for non-copyable types, a-la Boost.Optional's in-place factory
I found myself needing a variant of non-copyable types, and had to resort to dynamic allocation to solve it. Luckily for me, it wasn't a performance-critical code, so I could live with it. But of course it's not optimal...
4. add more flavours of get()
Currently the get<T>() function has the semantics of "return current value only if it is of type implicitly convertible to T". But there are other usages, even more useful than this (and if there are other get()s, the name of the original get() should be changed to, for example, get_implicit).
One of them is "return current value only if it is exactly of type T (using type_info)". The function will not compile if T is not any of the variant's types. A possible name for it could be get_exact(), but of course there might be better names.
Another usage I found useful is when all of the variant's types are implicitly convertible to a common T (e.g, they all inherit from T). This function will not compile if any of the variant's types can't be implicitly convertible to T, but if it does compile, then it ensures it can't fail in run-time (in contrast to get_implicit and get_exact, which can fail in run-time). A possible name is get_common().
I've attached a file containing my implementation of get_exact and get_common, in case you're interested. All the preprocessor magic there is beyond my understanding, I just copied it from your code, and hope I didn't make any mistakes...
5. support for reference types
I don't know if Boost.Variant currently supports reference types, nor am I certain it should, but it's a possibility... There was a debate about Boost.Optional's support for reference types, which reached no clear conclusion. Maybe you're familiar with the details, but if not, in short, the problem is with code such as
int a = ...; int b = ...; optional<int &> x; // x in initialized to 'empty' x = a; // x is binded to a's reference x = b; // is x rebinded to b's reference, or is a assigned the value in b?
The reason I bring it up here, is because I think it's relevant to Boost.Variant as well. Variant of reference types will raise the exact same questions, which should probably get the exact same answers. IOW, both the question of whether to support reference types, and the question of how to support them, should apply to both Boost.Optional and Boost.Variant uniformly. It's an open issue.
6. propose it for TR2
For the benefit of all of us...
Thanks again for a great library, Yuval
#ifndef VariantGet_h #define VariantGet_h
#include <Utils/Variant.h> #include <boost/mpl/assert.hpp> #include <boost/mpl/contains.hpp>
namespace boost {
template <typename T> struct get_common_visitor : public static_visitor<T> { T operator()(T t) { return t; } };
template <typename T, typename Variant> typename add_reference<T>::type get_common(Variant &v) { get_common_visitor<typename add_reference<T>::type> visitor; return v.apply_visitor(visitor); }
template <typename T, typename Variant> typename add_reference<const T>::type get_common(const Variant &v) { get_common_visitor<typename add_reference<const T>::type> visitor; return v.apply_visitor(visitor); }
template <typename T, typename Variant> void assert_variant_has_type(const Variant &) { BOOST_MPL_ASSERT((mpl::contains<typename Variant::types, T>)); }
template <typename T> struct get_exact_visitor : public static_visitor<T> { T operator()(T t) { return t; } };
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_pointer<U>::type get_exact( boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >* operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { assert_variant_has_type<U>(*operand); typedef typename add_pointer<U>::type U_ptr; if (!operand) return static_cast<U_ptr>(0); if (operand->type() != typeid(U)) return static_cast<U_ptr>(0);
detail::variant::get_visitor<U> v; return operand->apply_visitor(v); }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_pointer<const U>::type get_exact( const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >* operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { assert_variant_has_type<U>(*operand); typedef typename add_pointer<const U>::type U_ptr; if (!operand) return static_cast<U_ptr>(0); if (operand->type() != typeid(U)) return static_cast<U_ptr>(0);
detail::variant::get_visitor<const U> v; return operand->apply_visitor(v); }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_reference<U>::type get_exact( boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { typedef typename add_pointer<U>::type U_ptr; U_ptr result = get_exact<U>(&operand);
if (!result) throw bad_get(); return *result; }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_reference<const U>::type get_exact( const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { typedef typename add_pointer<const U>::type U_ptr; U_ptr result = get_exact<const U>(&operand);
if (!result) throw bad_get(); return *result; }
} // namespace
#endif /* ! defined VariantGet_h */
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Chris, I believe it was due to the complexity of implementing it :) Seriously though, it would be a nice feature to have, but it was not immediately obvious at the time how to easily implement "ternary" visitation. Moreover, there wasn't the same compelling set of use cases as there is with binary visitation. Let me know if there is, though. Eric Chris Weed wrote:
Hi, I'd also like to see apply_visitor with more arguments. Is there a reason it is limited to 2? Chris
On 6/5/06, Yuval Ronen <ronen_yuval@yahoo.com> wrote:
Hi Eric! It's good to see that you've got back to the list. For a long time now, there was no one to answer users' questions and comments, So I'm taking this (rare :-) ) opportunity to present my wish list for Boost.Variant.
First of all, I'd like to say that I think it's a great library. I've become addicted to it... However, there are some things I think are missing.
1. add operator !=
There's operator==, and adding operator!= would be both easy and useful.
2. apply_visitor should accept the visitor by value
std algorithms (such as for_each) accept their functors by value, not by [const] reference. I think that visitors should be considered as variant's equivalent to std functors, and thus be passed by value. This will allow me to pass temporary visitor objects, which is a very common use case, just as with std functors. It will also allow you to delete the two versions of apply_visitor (one taking a reference, one taking a const reference), and leave only one.
3. add support for non-copyable types, a-la Boost.Optional's in-place factory
I found myself needing a variant of non-copyable types, and had to resort to dynamic allocation to solve it. Luckily for me, it wasn't a performance-critical code, so I could live with it. But of course it's not optimal...
4. add more flavours of get()
Currently the get<T>() function has the semantics of "return current value only if it is of type implicitly convertible to T". But there are other usages, even more useful than this (and if there are other get()s, the name of the original get() should be changed to, for example, get_implicit).
One of them is "return current value only if it is exactly of type T (using type_info)". The function will not compile if T is not any of the variant's types. A possible name for it could be get_exact(), but of course there might be better names.
Another usage I found useful is when all of the variant's types are implicitly convertible to a common T (e.g, they all inherit from T). This function will not compile if any of the variant's types can't be implicitly convertible to T, but if it does compile, then it ensures it can't fail in run-time (in contrast to get_implicit and get_exact, which can fail in run-time). A possible name is get_common().
I've attached a file containing my implementation of get_exact and get_common, in case you're interested. All the preprocessor magic there is beyond my understanding, I just copied it from your code, and hope I didn't make any mistakes...
5. support for reference types
I don't know if Boost.Variant currently supports reference types, nor am I certain it should, but it's a possibility... There was a debate about Boost.Optional's support for reference types, which reached no clear conclusion. Maybe you're familiar with the details, but if not, in short, the problem is with code such as
int a = ...; int b = ...; optional<int &> x; // x in initialized to 'empty' x = a; // x is binded to a's reference x = b; // is x rebinded to b's reference, or is a assigned the value in b?
The reason I bring it up here, is because I think it's relevant to Boost.Variant as well. Variant of reference types will raise the exact same questions, which should probably get the exact same answers. IOW, both the question of whether to support reference types, and the question of how to support them, should apply to both Boost.Optional and Boost.Variant uniformly. It's an open issue.
6. propose it for TR2
For the benefit of all of us...
Thanks again for a great library, Yuval
#ifndef VariantGet_h #define VariantGet_h
#include <Utils/Variant.h> #include <boost/mpl/assert.hpp> #include <boost/mpl/contains.hpp>
namespace boost {
template <typename T> struct get_common_visitor : public static_visitor<T> { T operator()(T t) { return t; } };
template <typename T, typename Variant> typename add_reference<T>::type get_common(Variant &v) { get_common_visitor<typename add_reference<T>::type> visitor; return v.apply_visitor(visitor); }
template <typename T, typename Variant> typename add_reference<const T>::type get_common(const Variant &v) { get_common_visitor<typename add_reference<const T>::type> visitor; return v.apply_visitor(visitor); }
template <typename T, typename Variant> void assert_variant_has_type(const Variant &) { BOOST_MPL_ASSERT((mpl::contains<typename Variant::types, T>)); }
template <typename T> struct get_exact_visitor : public static_visitor<T> { T operator()(T t) { return t; } };
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_pointer<U>::type get_exact( boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >* operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { assert_variant_has_type<U>(*operand); typedef typename add_pointer<U>::type U_ptr; if (!operand) return static_cast<U_ptr>(0); if (operand->type() != typeid(U)) return static_cast<U_ptr>(0);
detail::variant::get_visitor<U> v; return operand->apply_visitor(v); }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_pointer<const U>::type get_exact( const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >* operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { assert_variant_has_type<U>(*operand); typedef typename add_pointer<const U>::type U_ptr; if (!operand) return static_cast<U_ptr>(0); if (operand->type() != typeid(U)) return static_cast<U_ptr>(0);
detail::variant::get_visitor<const U> v; return operand->apply_visitor(v); }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_reference<U>::type get_exact( boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { typedef typename add_pointer<U>::type U_ptr; U_ptr result = get_exact<U>(&operand);
if (!result) throw bad_get(); return *result; }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_reference<const U>::type get_exact( const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { typedef typename add_pointer<const U>::type U_ptr; U_ptr result = get_exact<const U>(&operand);
if (!result) throw bad_get(); return *result; }
} // namespace
#endif /* ! defined VariantGet_h */
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Hi Yuval, Thanks for your feedback. In particular, I would like to add support for in-place construction "sometime" :) As for operator!= (and operator<=, etc.), while I do think it may be useful, it raises the question of what requirements variant imposes on its bounded types. Specifically, adding support for these operators is not so straightforward as saying operator!= is simply !(*this == rhs). The idea of operator== and operator< is that if the current bounded type is the same in both variant instances, then operator overload _for that type_ will be invoked. To support this same behavior for the other operators would require adding to the bounded type requirements. See <http://boost.org/doc/html/variant/reference.html#variant.concepts> for a refresher on variant's approach to concepts. Maybe this is an overly-pedantic response on my part, though. The reason (or at least one of them) that apply_visitor accepts visitors by reference and not by value is that I had intended to introduce a dynamic_visitor counterpart to static_visitor in a more general Boost.Visitor library. Passing by value would interact poorly with polymorphic dynamic visitors due to slicing. If you are interested, I believe Boost.Visitor is probably still sitting around in the Boost Sandbox CVS on Sourceforge. Variant already supports reference types, at least on modern compilers. However, references are not assignable, and so as noted in the concepts documentation for variant, this implies that the resultant variant is not assignable. To address your comment about boost::optional, then, variant does not allow rebinding of references. I'll have to more carefully look at your proposals for additional get functions to see whether I am convinced of their utility. Eric Yuval Ronen wrote:
Hi Eric! It's good to see that you've got back to the list. For a long time now, there was no one to answer users' questions and comments, So I'm taking this (rare :-) ) opportunity to present my wish list for Boost.Variant.
First of all, I'd like to say that I think it's a great library. I've become addicted to it... However, there are some things I think are missing.
1. add operator !=
There's operator==, and adding operator!= would be both easy and useful.
2. apply_visitor should accept the visitor by value
std algorithms (such as for_each) accept their functors by value, not by [const] reference. I think that visitors should be considered as variant's equivalent to std functors, and thus be passed by value. This will allow me to pass temporary visitor objects, which is a very common use case, just as with std functors. It will also allow you to delete the two versions of apply_visitor (one taking a reference, one taking a const reference), and leave only one.
3. add support for non-copyable types, a-la Boost.Optional's in-place factory
I found myself needing a variant of non-copyable types, and had to resort to dynamic allocation to solve it. Luckily for me, it wasn't a performance-critical code, so I could live with it. But of course it's not optimal...
4. add more flavours of get()
Currently the get<T>() function has the semantics of "return current value only if it is of type implicitly convertible to T". But there are other usages, even more useful than this (and if there are other get()s, the name of the original get() should be changed to, for example, get_implicit).
One of them is "return current value only if it is exactly of type T (using type_info)". The function will not compile if T is not any of the variant's types. A possible name for it could be get_exact(), but of course there might be better names.
Another usage I found useful is when all of the variant's types are implicitly convertible to a common T (e.g, they all inherit from T). This function will not compile if any of the variant's types can't be implicitly convertible to T, but if it does compile, then it ensures it can't fail in run-time (in contrast to get_implicit and get_exact, which can fail in run-time). A possible name is get_common().
I've attached a file containing my implementation of get_exact and get_common, in case you're interested. All the preprocessor magic there is beyond my understanding, I just copied it from your code, and hope I didn't make any mistakes...
5. support for reference types
I don't know if Boost.Variant currently supports reference types, nor am I certain it should, but it's a possibility... There was a debate about Boost.Optional's support for reference types, which reached no clear conclusion. Maybe you're familiar with the details, but if not, in short, the problem is with code such as
int a = ...; int b = ...; optional<int &> x; // x in initialized to 'empty' x = a; // x is binded to a's reference x = b; // is x rebinded to b's reference, or is a assigned the value in b?
The reason I bring it up here, is because I think it's relevant to Boost.Variant as well. Variant of reference types will raise the exact same questions, which should probably get the exact same answers. IOW, both the question of whether to support reference types, and the question of how to support them, should apply to both Boost.Optional and Boost.Variant uniformly. It's an open issue.
6. propose it for TR2
For the benefit of all of us...
Thanks again for a great library, Yuval
------------------------------------------------------------------------
#ifndef VariantGet_h #define VariantGet_h
#include <Utils/Variant.h> #include <boost/mpl/assert.hpp> #include <boost/mpl/contains.hpp>
namespace boost {
template <typename T> struct get_common_visitor : public static_visitor<T> { T operator()(T t) { return t; } };
template <typename T, typename Variant> typename add_reference<T>::type get_common(Variant &v) { get_common_visitor<typename add_reference<T>::type> visitor; return v.apply_visitor(visitor); }
template <typename T, typename Variant> typename add_reference<const T>::type get_common(const Variant &v) { get_common_visitor<typename add_reference<const T>::type> visitor; return v.apply_visitor(visitor); }
template <typename T, typename Variant> void assert_variant_has_type(const Variant &) { BOOST_MPL_ASSERT((mpl::contains<typename Variant::types, T>)); }
template <typename T> struct get_exact_visitor : public static_visitor<T> { T operator()(T t) { return t; } };
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_pointer<U>::type get_exact( boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >* operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { assert_variant_has_type<U>(*operand); typedef typename add_pointer<U>::type U_ptr; if (!operand) return static_cast<U_ptr>(0); if (operand->type() != typeid(U)) return static_cast<U_ptr>(0);
detail::variant::get_visitor<U> v; return operand->apply_visitor(v); }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_pointer<const U>::type get_exact( const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >* operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { assert_variant_has_type<U>(*operand); typedef typename add_pointer<const U>::type U_ptr; if (!operand) return static_cast<U_ptr>(0); if (operand->type() != typeid(U)) return static_cast<U_ptr>(0);
detail::variant::get_visitor<const U> v; return operand->apply_visitor(v); }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_reference<U>::type get_exact( boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { typedef typename add_pointer<U>::type U_ptr; U_ptr result = get_exact<U>(&operand);
if (!result) throw bad_get(); return *result; }
template <typename U, BOOST_VARIANT_ENUM_PARAMS(typename T) > inline typename add_reference<const U>::type get_exact( const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& operand BOOST_VARIANT_AUX_GET_EXPLICIT_TEMPLATE_TYPE(U)) { typedef typename add_pointer<const U>::type U_ptr; U_ptr result = get_exact<const U>(&operand);
if (!result) throw bad_get(); return *result; }
} // namespace
#endif /* ! defined VariantGet_h */
------------------------------------------------------------------------
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Eric Friedman wrote:
As for operator!= (and operator<=, etc.), while I do think it may be useful, it raises the question of what requirements variant imposes on its bounded types. Specifically, adding support for these operators is not so straightforward as saying operator!= is simply !(*this == rhs). The idea of operator== and operator< is that if the current bounded type is the same in both variant instances, then operator overload _for that type_ will be invoked. To support this same behavior for the other operators would require adding to the bounded type requirements. See <http://boost.org/doc/html/variant/reference.html#variant.concepts> for a refresher on variant's approach to concepts. Maybe this is an overly-pedantic response on my part, though.
I think it *is* as straightforward as saying operator!= is simply !(*this == rhs). There is no other way that makes sense. Operators >, <=, >=, on the other hand, are completely different. I think there is no natural ordering of variant objects, so not supporting them is logical. Operator < is an exception because it's used for ordered conrainers. IOW, I completely agree with your decision to supply operator < and not supplying operators >, <=, >=.
The reason (or at least one of them) that apply_visitor accepts visitors by reference and not by value is that I had intended to introduce a dynamic_visitor counterpart to static_visitor in a more general Boost.Visitor library. Passing by value would interact poorly with polymorphic dynamic visitors due to slicing. If you are interested, I believe Boost.Visitor is probably still sitting around in the Boost Sandbox CVS on Sourceforge.
Then maybe different names should be used for dynamic_visitors, for example apply_dyn_visitor(). We need to consider whether the need for dynamic_visitors is large enough in order to justify the negative impact on the static_visitor's interface. Have you found there is great need for dynamic visitors? I have to admit that I've never needed them... Not to mention that the whole standard library is based on static functors passed by value...
Variant already supports reference types, at least on modern compilers. However, references are not assignable, and so as noted in the concepts documentation for variant, this implies that the resultant variant is not assignable. To address your comment about boost::optional, then, variant does not allow rebinding of references.
No problems here. I just want to express my opinion that the decision regarding reference support should apply both to Optional and to Variant.
I'll have to more carefully look at your proposals for additional get functions to see whether I am convinced of their utility.
I'd appreciate that very much. Thanks, Yuval

Yuval, Yuval Ronen wrote:
Eric Friedman wrote:
As for operator!= (and operator<=, etc.), while I do think it may be useful, it raises the question of what requirements variant imposes on its bounded types. Specifically, adding support for these operators is not so straightforward as saying operator!= is simply !(*this == rhs). The idea of operator== and operator< is that if the current bounded type is the same in both variant instances, then operator overload _for that type_ will be invoked. To support this same behavior for the other operators would require adding to the bounded type requirements. See <http://boost.org/doc/html/variant/reference.html#variant.concepts> for a refresher on variant's approach to concepts. Maybe this is an overly-pedantic response on my part, though.
I think it *is* as straightforward as saying operator!= is simply !(*this == rhs). There is no other way that makes sense.
I of course agree with you that this definition of operator!= makes sense, but I am reluctant because this is not how C++ works: the language allows entirely separate definitions of operator== and operator!=. It would seem unfortunate I think if variant called operator==(T,T) but never operator!=(T,T)...
Operators >, <=, >=, on the other hand, are completely different. I think there is no natural ordering of variant objects, so not supporting them is logical. Operator < is an exception because it's used for ordered conrainers. IOW, I completely agree with your decision to supply operator < and not supplying operators >, <=, >=.
Now how is that operator!= as !(lhs == rhs) makes sense, but operator<= as (lhs < rhs || lhs == rhs) is completely different?
The reason (or at least one of them) that apply_visitor accepts visitors by reference and not by value is that I had intended to introduce a dynamic_visitor counterpart to static_visitor in a more general Boost.Visitor library. Passing by value would interact poorly with polymorphic dynamic visitors due to slicing. If you are interested, I believe Boost.Visitor is probably still sitting around in the Boost Sandbox CVS on Sourceforge.
Then maybe different names should be used for dynamic_visitors, for example apply_dyn_visitor(). We need to consider whether the need for dynamic_visitors is large enough in order to justify the negative impact on the static_visitor's interface. Have you found there is great need for dynamic visitors? I have to admit that I've never needed them... Not to mention that the whole standard library is based on static functors passed by value...
I think my opinion here is that a visitor is fundamentally different from a functor. Functors model function types; they may be implemented with a template, but logically they have a single signature. By contrast, a visitor is a set of functions with different signatures used to handle a set of different cases. Also, looking back to your original email, you mentioned a desire to pass temporary visitor objects. I believe that is supported with the current implementation. The following compiles just fine under gcc 3.4: #include "boost/variant.hpp" class TestVisitor : public boost::static_visitor<> { public: template <typename T> void operator()(const T&) const { /*...*/ } }; int main() { boost::variant<int, double> var; apply_visitor(TestVisitor(), var); }
Variant already supports reference types, at least on modern compilers. However, references are not assignable, and so as noted in the concepts documentation for variant, this implies that the resultant variant is not assignable. To address your comment about boost::optional, then, variant does not allow rebinding of references.
No problems here. I just want to express my opinion that the decision regarding reference support should apply both to Optional and to Variant.
So you think Optional should change or Variant should change? Eric

Eric Friedman wrote:
As for operator!= (and operator<=, etc.), while I do think it may be useful, it raises the question of what requirements variant imposes on its bounded types. Specifically, adding support for these operators is not so straightforward as saying operator!= is simply !(*this == rhs). The idea of operator== and operator< is that if the current bounded type is the same in both variant instances, then operator overload _for that type_ will be invoked. To support this same behavior for the other operators would require adding to the bounded type requirements. See <http://boost.org/doc/html/variant/reference.html#variant.concepts> for a refresher on variant's approach to concepts. Maybe this is an overly-pedantic response on my part, though.
I think it *is* as straightforward as saying operator!= is simply !(*this == rhs). There is no other way that makes sense.
I of course agree with you that this definition of operator!= makes sense, but I am reluctant because this is not how C++ works: the language allows entirely separate definitions of operator== and operator!=. It would seem unfortunate I think if variant called operator==(T,T) but never operator!=(T,T)...
It's true that C++ allows for entirely separate definitions of operator== and operator!=, but we have to make the assumption that these definitions always compare for equality. In fact, you made this assumption yourself when implementing variant's operator==. The code that returns false if this->which() != rhs.which() is exactly such assumption - why return false and not some other thing? That said, if you choose to implement operator!= using T's operator!=, then I'd be completely cool with that.
Operators >, <=, >=, on the other hand, are completely different. I think there is no natural ordering of variant objects, so not supporting them is logical. Operator < is an exception because it's used for ordered conrainers. IOW, I completely agree with your decision to supply operator < and not supplying operators >, <=, >=.
Now how is that operator!= as !(lhs == rhs) makes sense, but operator<= as (lhs < rhs || lhs == rhs) is completely different?
Because operator== has only one purpose in C++: comparison for equality. Operator<, on the other hand, has two common roles. For classes that have natural ordering, it should define this ordering, and then all the other operators (> <= >=) should be defined as well. Classes that doesn't have this natural ordering, often still define operator< just to be able to be used in ordered containers. In such cases, operator< doesn't define a natural ordering because there isn't any. Instead, it just defines an ordering "that works" for ordered containers, nothing more. It doesn't need to compatible with operator== and !=, and defining operators >, <=, >= is misleading and should be avoided. boost::shared_ptr is a good example of such a class with no natural ordering, with operator== and !=, with operator<, but without the other operators. And shared_ptr is of TR1 strength... The question we need to ask, is whether variant has a natural ordering, and I think the answer is "no". All the rest is just a consequence of it.
The reason (or at least one of them) that apply_visitor accepts visitors by reference and not by value is that I had intended to introduce a dynamic_visitor counterpart to static_visitor in a more general Boost.Visitor library. Passing by value would interact poorly with polymorphic dynamic visitors due to slicing. If you are interested, I believe Boost.Visitor is probably still sitting around in the Boost Sandbox CVS on Sourceforge.
Then maybe different names should be used for dynamic_visitors, for example apply_dyn_visitor(). We need to consider whether the need for dynamic_visitors is large enough in order to justify the negative impact on the static_visitor's interface. Have you found there is great need for dynamic visitors? I have to admit that I've never needed them... Not to mention that the whole standard library is based on static functors passed by value...
I think my opinion here is that a visitor is fundamentally different from a functor. Functors model function types; they may be implemented with a template, but logically they have a single signature. By contrast, a visitor is a set of functions with different signatures used to handle a set of different cases.
I think the difference you rely on is of no significance. From the user's point of view, the functor passed to std::for_each is to be applied to the values in the container, just as the visitor should be applied to the value in the variant. The fact that there are several options, and hence there need to be several functions with different signatures, doesn't matter at all. IMO, the only thing the user is concerned about is passing his value to the function. Call it functor or visitor doesn't change anything, and may actually be misleading. So maybe only the dynamic_visitor is a visitor, and the static_visitor is just a variant_functor. That's my humble opinion.
Also, looking back to your original email, you mentioned a desire to pass temporary visitor objects. I believe that is supported with the current implementation. The following compiles just fine under gcc 3.4:
#include "boost/variant.hpp"
class TestVisitor : public boost::static_visitor<> { public:
template <typename T> void operator()(const T&) const { /*...*/ }
};
int main() { boost::variant<int, double> var; apply_visitor(TestVisitor(), var); }
If this code works, then it's great, sure (I find calling a member function a bit more elegant than calling a free function, but it's definitely not a deal-breaker), but it doesn't change the principal I tried to convince you of in the previous paragraph.
Variant already supports reference types, at least on modern compilers. However, references are not assignable, and so as noted in the concepts documentation for variant, this implies that the resultant variant is not assignable. To address your comment about boost::optional, then, variant does not allow rebinding of references.
No problems here. I just want to express my opinion that the decision regarding reference support should apply both to Optional and to Variant.
So you think Optional should change or Variant should change?
If only I knew... I have to say I don't really know the answer to this tough question. the only thing I do know, is that the decision should be variant/optional unanimous. Thanks again for taking the time to relate to my points, Yuval

[I know that this is from a thread that died a month ago, but I want to give advice to people who just want to add an "operator <" for ordered-container compatibility.] On 6/9/06 12:06 PM, "Yuval Ronen" <ronen_yuval@yahoo.com> wrote:
Eric Friedman wrote:
As for operator!= (and operator<=, etc.), while I do think it may be useful, it raises the question of what requirements variant imposes on its bounded types. Specifically, adding support for these operators is not so straightforward as saying operator!= is simply !(*this == rhs). The idea of operator== and operator< is that if the current bounded type is the same in both variant instances, then operator overload _for that type_ will be invoked. To support this same behavior for the other operators would require adding to the bounded type requirements. See <http://boost.org/doc/html/variant/reference.html#variant.concepts> for a refresher on variant's approach to concepts. Maybe this is an overly-pedantic response on my part, though.
I think it *is* as straightforward as saying operator!= is simply !(*this == rhs). There is no other way that makes sense.
I of course agree with you that this definition of operator!= makes sense, but I am reluctant because this is not how C++ works: the language allows entirely separate definitions of operator== and operator!=. It would seem unfortunate I think if variant called operator==(T,T) but never operator!=(T,T)...
It's true that C++ allows for entirely separate definitions of operator== and operator!=, but we have to make the assumption that these definitions always compare for equality. In fact, you made this assumption yourself when implementing variant's operator==. The code that returns false if this->which() != rhs.which() is exactly such assumption - why return false and not some other thing?
That said, if you choose to implement operator!= using T's operator!=, then I'd be completely cool with that.
Operators >, <=, >=, on the other hand, are completely different. I think there is no natural ordering of variant objects, so not supporting them is logical. Operator < is an exception because it's used for ordered conrainers. IOW, I completely agree with your decision to supply operator < and not supplying operators >, <=, >=.
Now how is that operator!= as !(lhs == rhs) makes sense, but operator<= as (lhs < rhs || lhs == rhs) is completely different?
Because operator== has only one purpose in C++: comparison for equality. Operator<, on the other hand, has two common roles.
For classes that have natural ordering, it should define this ordering, and then all the other operators (> <= >=) should be defined as well. Classes that doesn't have this natural ordering, often still define operator< just to be able to be used in ordered containers. In such cases, operator< doesn't define a natural ordering because there isn't any. Instead, it just defines an ordering "that works" for ordered containers, nothing more. It doesn't need to compatible with operator== and !=, and defining operators >, <=, >= is misleading and should be avoided.
The later role is invalid. The reason is that the version of STL that made it into the Standard specifies an extra parameter for ordered containers for a comparison operation. It allows two containers of the same element type to use different comparison criteria (at compile- and/or run-time). This invalidates the need for a fake "operator <" since you can always package the comparison routine in an extra parameter. Conversely, calling code must not hard code use of "operator <" or "std::less<>", unless that code requires types with a natural ordering. (The standard algorithms have variants that either assume "operator <" or take in a comparison parameter.) Making a fake "operator <" just for ordered containers invalidates the work of those who added comparison parameters in standard classes/functions. The issue is different for the "==" and "!=" operators because two objects can have their state checked for equivalence without having a standardized order. The "std::complex<>" template classes are an example.
boost::shared_ptr is a good example of such a class with no natural ordering, with operator== and !=, with operator<, but without the other operators. And shared_ptr is of TR1 strength...
Ordering for pointers is generally defined if both pointers are part of the same array segment. (Either one or both of the pointers can be at the "one-past-the-end" point of said segment.) If two "shared_ptr" objects can be used for points in an array segment[1], then comparison can be allowed with ALL four operators ("<", ">", "<=", and ">="). Otherwise, none of those operators should be defined and any current existence of such operators should be considered a bug.
The question we need to ask, is whether variant has a natural ordering, and I think the answer is "no". All the rest is just a consequence of it. [TRUNCATE]
[1] Obviously, such pointers can't use the standard "operator delete" since they couldn't be created with the standard single-new operator. -- Daryle Walker Mac, Internet, and Video Game Junkie darylew AT hotmail DOT com

Daryle Walker wrote:
Because operator== has only one purpose in C++: comparison for equality. Operator<, on the other hand, has two common roles.
For classes that have natural ordering, it should define this ordering, and then all the other operators (> <= >=) should be defined as well. Classes that doesn't have this natural ordering, often still define operator< just to be able to be used in ordered containers. In such cases, operator< doesn't define a natural ordering because there isn't any. Instead, it just defines an ordering "that works" for ordered containers, nothing more. It doesn't need to compatible with operator== and !=, and defining operators >, <=, >= is misleading and should be avoided.
The later role is invalid. The reason is that the version of STL that made it into the Standard specifies an extra parameter for ordered containers for a comparison operation. It allows two containers of the same element type to use different comparison criteria (at compile- and/or run-time). This invalidates the need for a fake "operator <" since you can always package the comparison routine in an extra parameter. Conversely, calling code must not hard code use of "operator <" or "std::less<>", unless that code requires types with a natural ordering. (The standard algorithms have variants that either assume "operator <" or take in a comparison parameter.)
Making a fake "operator <" just for ordered containers invalidates the work of those who added comparison parameters in standard classes/functions.
You have a point there, no doubt. On the other hand, Library writers should supply some kind of functor to be used with their types in conjunction with ordered containers (when appropriate, and boost::variant is of course appropriate). How should this functor be called? You might say there should be some convention, but there isn't any right now, and some libraries use operator< for this purpose. Is it that bad if operator< is the convention? I'm not sure. It's sure is convenient to use ordered containers like that... And it seems to me that there is no major drawback to this, because no other order operator is provided. But I guess it's highly dependent on taste...
The issue is different for the "==" and "!=" operators because two objects can have their state checked for equivalence without having a standardized order. The "std::complex<>" template classes are an example.
std::complex doesn't have operator<. std::tr1::shared_ptr does. So it seems the standard isn't very helpfully consistent here...
boost::shared_ptr is a good example of such a class with no natural ordering, with operator== and !=, with operator<, but without the other operators. And shared_ptr is of TR1 strength...
Ordering for pointers is generally defined if both pointers are part of the same array segment. (Either one or both of the pointers can be at the "one-past-the-end" point of said segment.) If two "shared_ptr" objects can be used for points in an array segment[1], then comparison can be allowed with ALL four operators ("<", ">", "<=", and ">="). Otherwise, none of those operators should be defined and any current existence of such operators should be considered a bug.
I didn't quite understand that last paragraph. shared_ptrs doesn't have to point to objects in array (actually, they better not), but there can be arrays of shared_ptrs. So should the shared_ptr class provide order operators or shouldn't it? Anyway, the committee already made up it mind about the answer to this question...

On 7/5/06 1:38 PM, "Yuval Ronen" <ronen_yuval@yahoo.com> wrote:
Daryle Walker wrote: [Yuval wrote:]
Because operator== has only one purpose in C++: comparison for equality. Operator<, on the other hand, has two common roles.
For classes that have natural ordering, it should define this ordering, and then all the other operators (> <= >=) should be defined as well. Classes that doesn't have this natural ordering, often still define operator< just to be able to be used in ordered containers. In such cases, operator< doesn't define a natural ordering because there isn't any. Instead, it just defines an ordering "that works" for ordered containers, nothing more. It doesn't need to compatible with operator== and !=, and defining operators >, <=, >= is misleading and should be avoided.
The later role is invalid. The reason is that the version of STL that made it into the Standard specifies an extra parameter for ordered containers for a comparison operation. It allows two containers of the same element type to use different comparison criteria (at compile- and/or run-time). This invalidates the need for a fake "operator <" since you can always package the comparison routine in an extra parameter. Conversely, calling code must not hard code use of "operator <" or "std::less<>", unless that code requires types with a natural ordering. (The standard algorithms have variants that either assume "operator <" or take in a comparison parameter.)
Making a fake "operator <" just for ordered containers invalidates the work of those who added comparison parameters in standard classes/functions.
You have a point there, no doubt. On the other hand, Library writers should supply some kind of functor to be used with their types in conjunction with ordered containers (when appropriate, and boost::variant is of course appropriate). How should this functor be called? You might say there should be some convention, but there isn't any right now, and some libraries use operator< for this purpose. Is it that bad if operator< is the convention? I'm not sure. It's sure is convenient to use ordered containers like that... And it seems to me that there is no major drawback to this, because no other order operator is provided. But I guess it's highly dependent on taste...
C++ classes are supposed to model some user concept. If that concept doesn't support ordering then it should not have the ordering operators. Adding those operators for "compatibility" with standard containers is lying to the user by implying an inappropriate capability. I think the problem stems from another trap: default template parameters are Golden and Must Never Be Overridden. Default parameters can be convenient, but you must not use them if they are inappropriate. If they were supposed to be inviolate, wouldn't they would have been hard-coded? To answer your question, I don't think there should be a convention for a non-ordered type so the author can choose the most descriptive name. This name can't be automated because the current automation convention involves using "operator <" and/or "std::less<>", which we assume aren't defined for our non-ordered type. Due to no automation, the user always has to fill in the author's comparison type. Aside: this controversy assumes that there is only one fake ordering possible. What if there's more than one equally good fake order? Do you bless just one? Or do you offer all of them?
The issue is different for the "==" and "!=" operators because two objects can have their state checked for equivalence without having a standardized order. The "std::complex<>" template classes are an example.
std::complex doesn't have operator<. std::tr1::shared_ptr does. So it seems the standard isn't very helpfully consistent here...
Looking at <http://www.boost.org/libs/smart_ptr/shared_ptr.htm>, i.e. the Boost version, it seems that the authors fell into the "fake operator <" trap. If any of those authors are reading this, can you tell us why you added the fake operator instead of a custom-named comparison class?
boost::shared_ptr is a good example of such a class with no natural ordering, with operator== and !=, with operator<, but without the other operators. And shared_ptr is of TR1 strength...
Ordering for pointers is generally defined if both pointers are part of the same array segment. (Either one or both of the pointers can be at the "one-past-the-end" point of said segment.) If two "shared_ptr" objects can be used for points in an array segment[1], then comparison can be allowed with ALL four operators ("<", ">", "<=", and ">="). Otherwise, none of those operators should be defined and any current existence of such operators should be considered a bug.
I didn't quite understand that last paragraph. shared_ptrs doesn't have to point to objects in array (actually, they better not), but there can be arrays of shared_ptrs. So should the shared_ptr class provide order operators or shouldn't it? Anyway, the committee already made up it mind about the answer to this question...
I meant your first case, the one you stated as "shared_ptrs doesn't have to point to objects in array (actually, they better not)". As long as the shared_ptr uses a do-nothing delete function object, I think using it with objects from an array segment is OK. This case is the only time I would feel OK with an "operator <" for a shared_ptr, because it would be a true ordering operator. We would have to add the other three ordering operators to complete ordering capabilities of the model. As is, though, the current operator is a fake; it doesn't even have to be compatible with "std::less<>" (assuming the TR1 shared_ptr is just like the Boost one). Unfortunately, it seems that the committee made a mistake and is endorsing an bad idiom. -- Daryle Walker Mac, Internet, and Video Game Junkie darylew AT hotmail DOT com

Daryle Walker wrote:
Looking at <http://www.boost.org/libs/smart_ptr/shared_ptr.htm>, i.e. the Boost version, it seems that the authors fell into the "fake operator <" trap. If any of those authors are reading this, can you tell us why you added the fake operator instead of a custom-named comparison class?
shared_ptr defines operator< for map<shared_ptr<>, V> to work. What do you mean by "fake"?

On Mon, 10 Jul 2006 23:52:45 +0300 "Peter Dimov" <pdimov@mmltd.net> wrote:
Daryle Walker wrote:
Looking at <http://www.boost.org/libs/smart_ptr/shared_ptr.htm>, i.e. the Boost version, it seems that the authors fell into the "fake operator <" trap. If any of those authors are reading this, can you tell us why you added the fake operator instead of a custom-named comparison class?
shared_ptr defines operator< for map<shared_ptr<>, V> to work. What do you mean by "fake"?
I think that's what he means by "fake." You defined an operator<() so that it could be used with an STL container using the "default" comparator, while providing a shared-ptr-less-than-by-comparing-the-pointer comparator would be more appropriate. Of course, it would require passing the comparator instaed of using the default std::less, but it does not introduce an operator<() that may not be appropriate for some cases of shared_ptr<>.

Jody Hagins wrote:
On Mon, 10 Jul 2006 23:52:45 +0300 "Peter Dimov" <pdimov@mmltd.net> wrote:
Daryle Walker wrote:
Looking at <http://www.boost.org/libs/smart_ptr/shared_ptr.htm>, i.e. the Boost version, it seems that the authors fell into the "fake operator <" trap. If any of those authors are reading this, can you tell us why you added the fake operator instead of a custom-named comparison class?
shared_ptr defines operator< for map<shared_ptr<>, V> to work. What do you mean by "fake"?
I think that's what he means by "fake." You defined an operator<() so that it could be used with an STL container using the "default" comparator, while providing a shared-ptr-less-than-by-comparing-the-pointer comparator would be more appropriate.
I know what I did, but this still doesn't tell me what "fake" means in this context. What objective properties make an operator< "fake"?

On 7/10/06 4:52 PM, "Peter Dimov" <pdimov@mmltd.net> wrote:
Daryle Walker wrote:
Looking at <http://www.boost.org/libs/smart_ptr/shared_ptr.htm>, i.e. the Boost version, it seems that the authors fell into the "fake operator <" trap. If any of those authors are reading this, can you tell us why you added the fake operator instead of a custom-named comparison class?
shared_ptr defines operator< for map<shared_ptr<>, V> to work. What do you mean by "fake"?
I mean just what you did, defining such an operator _only_ to allow associative containers to use a default template argument, even if the operator doesn't fit the class's model otherwise. If we take your view to the extreme, then why should that template argument for comparison exist at all? We could always define an operator "<" and let associative containers use that. (Of course, such an action would forbid containers with the same key type but different comparison criteria.) What's wrong with defining a separate comparison class? Having to specify the class's name does not change the computation complexity, only your amount of typing (and you can use a typedef for multiple uses of the name). Typing a model involves not only what aspects to include, but what to exclude. Don't corrupt the model for convenience. Finally, using the comparison argument localizes the criteria. The whole operator "<" alternative makes the decision global, even for those that don't want/need it. I view the fake operator "<" as a code smell, and probably should be added to any official lists. -- Daryle Walker Mac, Internet, and Video Game Junkie darylew AT hotmail DOT com
participants (6)
-
Chris Weed
-
Daryle Walker
-
Eric Friedman
-
Jody Hagins
-
Peter Dimov
-
Yuval Ronen