Re: [boost] An extension to boost::variant

Chris Hamilton-3 wrote:
While boost::variant is very capable at what it does, there is one thing it doesn't do ..
..My question is this: why can't boost::variant be clever about reference data types..
The reason I want this, is to have a nice String variant, that encapsulates the various forms a string can take in a current project: std::strings, char*'s, and std::string* and boost::shared_ptr<std::string>. I want functions to be able to handle all of these types transparently by using variants as the argument types. Allowing "const std::string&" as a variant type allows this to be done while not requiring a wasteful string copy. .. (I have a small custom variant class implementing this, and it appears to work well. It's not as 'full fledged' as the boost::variant class, but the approach I've taken could surely be grafted on to boost::variant.)
An example will be better than a lengthy explanation, please have a look at the test-cases in the boost distribution: libs/variant/test/: specially variant_reference_test.cpp IMHO, it will answer all your queries. -Cat -- View this message in context: http://www.nabble.com/An-extension-to-boost%3A%3Avariant-tp21881442p21896688... Sent from the Boost - Dev mailing list archive at Nabble.com.

..My question is this: why can't boost::variant be clever about reference data types..
An example will be better than a lengthy explanation, please have a look at the test-cases in the boost distribution: libs/variant/test/: specially variant_reference_test.cpp
IMHO, it will answer all your queries.
Hmm... that doesn't seem to answer my question. boost::variant currently can not handle the following code: struct foo {}; int main(int argc, const char** argv) { foo f; foo* pf = &f; boost::variant<foo*,foo&> v; v = f; printf("%d\n", &boost::get<foo&>(v) == &f); v = pf; printf("%d\n", boost::get<foo*>(v) == pf); pf = NULL; printf("%d\n", boost::get<foo*>(v) != pf) return 0; } My question is essentially this: why couldn't it? Regards, Chris

Chris Hamilton wrote:
boost::variant currently can not handle the following code: [...] boost::variant<foo*,foo&> v; [...] My question is essentially this: why couldn't it?
I suppose it requires the types to be value types, otherwise assignment and copy don't make sense. It's just like containers really. What's wrong with using T* or reference_wrapper<T> instead of T&?

boost::variant currently can not handle the following code: [...] boost::variant<foo*,foo&> v; [...] My question is essentially this: why couldn't it?
I suppose it requires the types to be value types, otherwise assignment and copy don't make sense. It's just like containers really. What's wrong with using T* or reference_wrapper<T> instead of T&?
reference_wrapper *almost* does what I want it to, but it requires conscious effort on behalf of the user. For example, the following compiles and runs: struct foo {}; int main(int argc, const char** argv) { foo f; foo* pf = &f; boost::variant<foo*,boost::reference_wrapper<foo> > v; v = boost::reference_wrapper<foo>(f); printf("%d\n", & (boost::get<boost::reference_wrapper<foo>
(v).get() ) == &f);
v = pf; printf("%d\n", boost::get<foo*>(v) == pf); pf = NULL; printf("%d\n", boost::get<foo*>(v) != pf) return 0; } The problem is that the user has to be aware that they are passing a reference by explicitly wrapping it. The problem is that boost::variant doesn't know how to handle an '= instance_of_foo', and realize this means it should be using the boost::reference_wrapper<foo> type. Cheers, Chris

reference_wrapper *almost* does what I want it to, but it requires conscious effort on behalf of the user.
Yes, if the user here is user of the variant: that is you. No, if the user here is user of your code: of course you will develop skeleton wrapper over variant (in 3-5 lines) before giving it the user of your code. In Heaven, -Cat

v = boost::reference_wrapper<foo>(f);
You can also write that as
v = boost::ref(f);
Thanks, I hadn't actually seen the reference_wrapper library before, so didn't look too deeply (just far enough to know that passing the raw object didn't work). So, effectively, I'm proposing a small addition to boost::variant, in some sense 'embedding' the boost::reference_wrapper functionality into it, and applying a little syntactic sugar. Does anybody see any value in this, or is it just me? (In the meantime, I can always use a small wrapper to boost::variant that adds these changes.) Chris

..My question is this: why can't boost::variant be clever about reference data types..
To quote from the documentation of boost::variant: http://www.boost.org/doc/libs/1_38_0/doc/html/variant/reference.html#variant... Every type specified as a template argument to variant<http://www.boost.org/doc/libs/1_38_0/doc/html/boost/variant.html>must at minimum fulfill the above requirements. In addition, certain features of variant are available only if its bounded types meet the requirements of these following additional concepts: - Assignable<http://www.boost.org/doc/libs/1_38_0/doc/html/Assignable.html>: variant is itself *Assignable* if and only if every one of its bounded types meets the requirements of the concept. (Note that top-level const-qualified types and reference types do *not* meet these requirements.) So, boost::variant can not handle simple references by design(we will discuss about the rational behind it soon once I have understood your requirements more clearly). Though if you insist on some way out with your intended usage sematics, then let me think based on my understanding of your requirement so far.
An example will be better than a lengthy explanation, please have a look at the test-cases in the boost distribution: libs/variant/test/: specially variant_reference_test.cpp
IMHO, it will answer all your queries.
Hmm... that doesn't seem to answer my question. boost::variant currently can not handle the following code:
struct foo {};
int main(int argc, const char** argv) { foo f; foo* pf = &f;
boost::variant<foo*,foo&> v;
v = f;
This is an error, because the declaration of variant above doesn't expect type of foo. So, probably you want to write: boost::variant < foo, foo *, foo & > v; //extra indentation for better readability. then, v = f; Hold on, this is still not allowed :). I know this is what you want to know why! So, you have to find a way to first get the job done then we may discuss the rest of why! Quoting from Boost.Ref : http://www.boost.org/doc/libs/1_38_0/doc/html/ref.html: "The Ref library is a small library that is useful for passing references to function templates (algorithms) that would usually take copies of their arguments." So, you would like to write instead : boost::variant<foo, foo*, boost::reference_wrapper<foo> > v; //<boost/ref.hpp> Now it is ok. only then you can write: v = f; // now it is fine.
printf("%d\n", &boost::get<foo&>(v) == &f);
The above code is again in error because you have not yet given "foo &" to your variant. Though it will be a run-time error(I mean variant will throw an exception boost::bad_get which you need to catch and gracefully handle n exit from here) So, having said this, let me guess again (why I have to do this again.. :-( ) what you want to write instead may be this: printf("%d\n", &boost::get<foo>(v) == &f); If you meant it, then I am more confused why you want to do this, knowing that these 2 addresses can never be equal ??
v = pf; printf("%d\n", boost::get<foo*>(v) == pf);
pf = NULL; printf("%d\n", boost::get<foo*>(v) != pf)
you have missed a semicolon here :) (did you really try this code on your machine before posting ? ) Now it looks like everything is fine as per the job in question.
return 0; }
My question is essentially this: why couldn't it?
Now why? It is deceitfully simple design decision made by the author. For now, I will like to quote from "http://www.boost.org/doc/libs/1_38_0/doc/html/ref/ack.html: "ref<http://www.boost.org/doc/libs/1_38_0/doc/html/boost/reference_wrapper.html#boost.ref_id2748655>and cref<http://www.boost.org/doc/libs/1_38_0/doc/html/boost/reference_wrapper.html#boost.cref_id3002915>were originally part of the Tuple <http://www.boost.org/doc/libs/1_38_0/libs/tuple/index.html> library by Jaakko Järvi. They were "promoted to boost:: status" by Peter Dimov because they are generally useful. Douglas Gregor and Dave Abrahams contributed is_reference_wrapper<http://www.boost.org/doc/libs/1_38_0/doc/html/boost/is_reference_wrapper.html>and unwrap_reference<http://www.boost.org/doc/libs/1_38_0/doc/html/boost/unwrap_reference.html> ."
Regards,
Chris
HTH for now, -Cat

To quote from the documentation of boost::variant:
http://www.boost.org/doc/libs/1_38_0/doc/html/variant/reference.html#variant...
Every type specified as a template argument to variant<http://www.boost.org/doc/libs/1_38_0/doc/html/boost/variant.html>must at minimum fulfill the above requirements. In addition, certain features of variant are available only if its bounded types meet the requirements of these following additional concepts:
- Assignable<http://www.boost.org/doc/libs/1_38_0/doc/html/Assignable.html>: variant is itself *Assignable* if and only if every one of its bounded types meets the requirements of the concept. (Note that top-level const-qualified types and reference types do *not* meet these requirements.)
So, boost::variant can not handle simple references by design(we will discuss about the rational behind it soon once I have understood your requirements more clearly).
I'm quite aware of what the boost docs say, and what I'm proposing is an extension to the variant class such that references *can* be handled (and have it satisfy the Assignable and default constructible concepts, at least as far as the internals of the variant object are concerned, by internally storing a pointer). Caveat: There would be a small violation to the "Never Empty Guarantee" if a reference was the first templated type and the variant was default constructed; in this case, it would be default constructible (as internally it would be stored using a pointer), but the pointer would have a null value. Trying to use this value would cause an error, obviously.
Hmm... that doesn't seem to answer my question. boost::variant currently can not handle the following code:
struct foo {};
int main(int argc, const char** argv) { foo f; foo* pf = &f;
boost::variant<foo*,foo&> v;
v = f;
This is an error, because the declaration of variant above doesn't expect type of foo.
No, but I want it to expect a type of *reference* to foo, in which case the above line would be just fine.
v = f; // now it is fine.
No, in fact this is not fine (did you try compiling your code?). If it were, I'd have no problem using boost::reference_wrapper. The problem remains: the variant<...> class doesn't know how to handle 'operator=( foo& f )'.
you have missed a semicolon here :) (did you really try this code on your machine before posting ? )
Indeed... and it doesn't compile (even with the semi-colon, which was a copy and paste error); that was the whole point of the exercise. Chris

what I'm proposing is an extension to the variant class such that references *can* be handled (and have it satisfy the Assignable and default constructible concepts, at least as far as the internals of the variant object are concerned, by internally storing a pointer).
Caveat: There would be a small violation to the "Never Empty Guarantee" if a reference was the first templated type and the variant was default constructed; in this case, it would be default constructible (as internally it would be stored using a pointer), but the pointer would have a null value. Trying to use this value would cause an error, obviously.
So you are consciously asking for something which you will know will pose a threat to the existing promises laid by the present variant. Interesting!
The problem remains: the variant<...> class doesn't know how to handle 'operator=( foo& f )'.
It is not a problem I think rather it is feature in the light the caveat(just one of many such worms) you mentioned above. So by now, you have learned that you can gladly use variant like boost::variant<foo*,boost::reference_wrapper<foo> > v. Now to the question of "The problem is that the user has to be aware that they are passing a reference by explicitly wrapping it.": To achieve this, you can easily cook up some wrapper which will do the type forwarding magic by using type traits gimicks like is_reference on the user type before passing it to variant. So, now you will like to say : it is good that boost::variant doesn't know how to handle an '= instance_of_foo', and realize this means : you have to read and understand other boost libs too(for writing metaprograms based wrapper) to write a skeleton wrapper to help your user. HTH and you have somewhat better understanding of boost libs and its rationale. If you are looking for nice pdf documentation, here you go: http://boost.cowic.de/rc/pdf/ Enjoy reading before posting! -Cat

Caveat: There would be a small violation to the "Never Empty Guarantee" if a reference was the first templated type and the variant was default constructed; in this case, it would be default constructible (as internally it would be stored using a pointer), but the pointer would have a null value. Trying to use this value would cause an error, obviously.
So you are consciously asking for something which you will know will pose a threat to the existing promises laid by the present variant. Interesting!
If you'd read what I just said, this would only be in the case where the reference type was used as the first template parameter. It is already stated in the documentation that the resulting boost::variant is only default constructible if the first type is; the "never empty guarantee" would similarly only stand if the first type is not a reference (and, in fact there are other ways around even this). So, it doesn't change the behaviour of any existing valid code using boost::variant, and would at most add another byline to the 'concepts' documentation. No need to get too excited over this...
The problem remains: the variant<...> class doesn't know how to handle 'operator=( foo& f )'.
It is not a problem I think rather it is feature in the light the caveat(just one of many such worms) you mentioned above.
So by now, you have learned that you can gladly use variant like boost::variant<foo*,boost::reference_wrapper<foo> > v.
Now to the question of "The problem is that the user has to be aware that they are passing a reference by explicitly wrapping it.":
To achieve this, you can easily cook up some wrapper which will do the type forwarding magic by using type traits gimicks like is_reference on the user type before passing it to variant.
Once again, in earlier mails I stated that I want to use boost::variant for *runtime polymorphism*. That is, the boost::variant<...> would be an argument type in a non-templated function. This function is meant to be exposed directly to the user in an API. Thus, for each of these functions, I'd have to wrap it in a templated wrapper which does the type_traits magic. Why not address it in a single place, ie: in the boost::variants class? I'm a little tired of running in circles here. Anybody else care to weigh in on the issue? Cheers, Chris

It is already stated in the documentation that the resulting boost::variant ..
No need to get
too excited over this...
Please read the documentation again and you will find the same answer already given to you.
I want to use boost::variant for *runtime polymorphism*. That is, the boost::variant<...> would be an argument type in a non-templated function. This function is meant to be exposed directly to the user in an API. Thus, for each of these functions, I'd have to wrap it in a templated wrapper which does the type_traits magic. Why not address it in a single place, ie: in the boost::variants class?
I'm a little tired of running in circles here. Anybody else care to weigh in on the issue?
May be someone will do the complete home work for you. Here people are very liberal n giving. -Cat

AMDG Chris Hamilton wrote:
Caveat: There would be a small violation to the "Never Empty Guarantee" if a reference was the first templated type and the variant was default constructed; in this case, it would be default constructible (as internally it would be stored using a pointer), but the pointer would have a null value. Trying to use this value would cause an error, obviously.
Just forbid default construction in this case. It isn't very hard. In Christ, Steven Watanabe

Chris Hamilton wrote:
Caveat: There would be a small violation to the "Never Empty Guarantee" if a reference was the first templated type and the variant was default constructed; in this case, it would be default constructible (as internally it would be stored using a pointer), but the pointer would have a null value. Trying to use this value would cause an error, obviously.
Just forbid default construction in this case. It isn't very hard.
Okay. So, I dug into the variant source code to see how hard this would be to implement. In my mind, it came down to the following: (1) variant currently wraps reference types with reference_content<>. Instead, make it wrap them with reference_wrapper. (2) Replace all reference_content<> handling throughout variant with reference_wrapper<> handlers. (3) Make reference_wrapper support operator= (by removing the explicit from the constructors). reference_wrappers already forbid default construction, so the above (quoted discussion) is a non-issue. (4) Add second versions of the variant assign, constructor, etc, functions that can pass non-const references (so non-const reference types can be handled as variant input types). This now lets me do something like: // The reference type is the second one, so that this object // can be default constructible. typedef boost::variant<const char *, const std::string &> String; const char * s1 = "string1"; std::string s2("string2"); String s; // This doesn't throw s = s1; boost::get<const char *>(s); // Nor does this. s = s2; boost::get<const std::string&>(s); Anybody see the utility of this? (In my mind, this is a feature I want variants to have.) Anybody see any problems with the approach I've taken? I haven't run the entire variant test suite yet, nor have I explored all possible uses (and misuses) of reference types, but I would appreciate some early feedback. I've attached a unified patch (taken against boost-trunk). Cheers, Chris
participants (5)
-
Catalytic
-
Chandrashekhar Kumar
-
Chris Hamilton
-
Mathias Gaunard
-
Steven Watanabe