[variant] Warning: variadic templates in boost::variant
Development version of Boost.Variant library now uses C++11 variadic
templates when possible.
This leads to more compact binaries, more readable compiler error messages
and faster compilation times.
However this:
* breaks ABI (now variant is declared as `template
Antony Polukhin вроте:
However this: * breaks ABI (now variant is declared as `template
class variant`)
Why not just template
On Monday 09 December 2013 14:02:12 Peter Dimov wrote:
Antony Polukhin вроте:
However this: * breaks ABI (now variant is declared as `template
class variant`) Why not just template
class variant? It's true that the degenerate case of variant<> isn't particularly useful, but then again, neither is the singular case of variant<X>. Is there a good reason to prohibit variant<>?
What should be the storage for such a variant then? Also, most of the interface of variant<> becomes invalid (e.g. type(), which(), assignment from elements, apply_visitor()). With such drastic interface differences, there should be a compelling reason to have such a specialization, and I don't see one.
Andrey Semashev wrote:
On Monday 09 December 2013 14:02:12 Peter Dimov wrote:
Why not just template
class variant?
What should be the storage for such a variant then? Also, most of the interface of variant<> becomes invalid (e.g. type(), which(), assignment from elements, apply_visitor()). With such drastic interface differences, there should be a compelling reason to have such a specialization, and I don't see one.
I agree that which() is a problem.
2013/12/9 Peter Dimov
Andrey Semashev wrote:
On Monday 09 December 2013 14:02:12 Peter Dimov wrote:
Why not just template
class variant? What should be the storage for such a variant then? Also, most of the
interface of variant<> becomes invalid (e.g. type(), which(), assignment from elements, apply_visitor()). With such drastic interface differences, there should be a compelling reason to have such a specialization, and I don't see one.
I agree that which() is a problem.
I can easily add `template <> class variantboost::mpl::void_ {}; ` and `template <> class variant<> {};` class specializations that have no methods. Somebody needs them? -- Best regards, Antony Polukhin
AMDG On 12/09/2013 04:16 AM, Andrey Semashev wrote:
On Monday 09 December 2013 14:02:12 Peter Dimov wrote:
Antony Polukhin вроте:
However this: * breaks ABI (now variant is declared as `template
class variant`) Why not just template
class variant? It's true that the degenerate case of variant<> isn't particularly useful, but then again, neither is the singular case of variant<X>. Is there a good reason to prohibit variant<>? What should be the storage for such a variant then? Also, most of the interface of variant<> becomes invalid (e.g. type(), which(), assignment from elements, apply_visitor()). With such drastic interface differences, there should be a compelling reason to have such a specialization, and I don't see one.
Boost.Variant explicitly guarantees that it always holds one of the variant types. variant<> doesn't really make sense, because it's impossible to satisfy this invariant. variant<X> isn't very useful, but it does have a sensible meaning. In Christ, Steven Watanabe
On 12/09/13 08:52, Steven Watanabe wrote: [snip]> variant<> doesn't
really make sense, because it's impossible to satisfy this invariant.
I'm not as sure variant<> doesn't make some sense. tuple<> makes sense, at least according to: http://en.wikipedia.org/wiki/Tuple and variant<> is a "dual" to tuple: http://en.wikipedia.org/wiki/Dual_%28category_theory%29 Also the following: http://en.wikipedia.org/wiki/Coproduct says: The coproduct indexed by the empty set (that is, an empty coproduct) is the same as an initial object in C. where C is some category and "coproduct", IIUC, is what variant is (the Coproduct page mentions it at a "disjoint union of sets"). Hence, at least the Coproduct page sees some merit in defining an "empty coproduct" or variant<>. -regards, Larry
AMDG On 12/09/2013 09:31 AM, Larry Evans wrote:
On 12/09/13 08:52, Steven Watanabe wrote: [snip]> variant<> doesn't
really make sense, because it's impossible to satisfy this invariant.
I'm not as sure variant<> doesn't make some sense. tuple<> makes sense, at least according to:
The difference between tuple<> and variant<>, is that tuple<> has exactly one possible value, but variant<> has no possible values. In Christ, Steven Watanabe
Steven Watanabe wrote:
The difference between tuple<> and variant<> is that tuple<> has exactly one possible value, but variant<> has no possible values.
Not conceptually. tuple
On Mon, Dec 9, 2013 at 12:28 PM, Peter Dimov
Steven Watanabe wrote:
The difference between tuple<> and variant<> is that tuple<> has exactly one possible value, but variant<> has no possible values.
Not conceptually. tuple
is struct { X x; Y y; }. variant is union { X x; Y y; }. tuple<> is struct {}. variant<> is union {}. All are valid. It's not that important though. The current variant doesn't support this case, so there's not much reason for the variadic one to do so. Sorry for bringing it up.
I'm going to have to back you up here that a variant<> does sometimes make sense and I don't think we'd lose anything by supporting it. In real, non-hypohetical code, I've encountered a place where I had a 0-element variant come up and had to special-case my code so that it would work as expected. The way it came up was I had a variant of several types (the type list was the result of some metaprogramming) and a visitor of that variant that returned the result of a member function call on the currently stored object, whichever one it was (each type in the variant had the same named function but the return type wasn't necessarily the same). I constructed the return type based on the return types of the functions in question, eliminating any duplicates. If one of the functions returned a void type, no type was added to the list. I stored this result and later on did things with it (such as display it). What this meant in practice was that I was sometimes left with 0-element and 1-element variants. It would be nice if the 0 case just worked as expected rather than having to special-case. As well, in the case of a single-element variant, it would be nice if the ID weren't explicitly stored since it would always be the same value (I don't remember if Boost.Variant makes this optimization). If there is some ambiguity with how a 0-element variant should be handled, then I'd say perhaps we should leave it as a compile-time error, but I think that in practice you'd just expect it to be a stateless type that either always does nothing when visited, passes some kind of special tag type when visited (this might be better since it would work with n-ary visitation), or simply produces a compile-time error when visited. I don't know what this means with respect to Variant's "never-empty" guarantee or the result of .empty(), but intuitively I'd just say that a variant satisfies the never-empty guarantee iff the amount of types it is over is greater than 0. In the 0 case, empty() would return false (it should also be static constexpr in any case). -- -Matt Calabrese
On 12/09/13 22:36, Matt Calabrese wrote:> On Mon, Dec 9, 2013 at 12:28
PM, Peter Dimov
Steven Watanabe wrote:
The difference between tuple<> and variant<> is that tuple<> has
one possible value, but variant<> has no possible values.
Not conceptually. tuple
is struct { X x; Y y; }. variant is union { X x; Y y; }. tuple<> is struct {}. variant<> is union {}. All are valid. It's not that important though. The current variant doesn't support this case, so there's not much reason for the variadic one to do so. Sorry for bringing it up.
I'm going to have to back you up here that a variant<> does sometimes make sense and I don't think we'd lose anything by supporting it. In real, non-hypohetical code, I've encountered a place where I had a 0-element variant come up and had to special-case my code so that it would work as expected. The way it came up was I had a variant of several types (the type list was the result of some metaprogramming) and a visitor of that variant that returned the result of a member function call on the currently stored object, whichever one it was (each type in the variant had the same named function but the return type wasn't necessarily the same). I constructed the return type based on the return types of the functions in question, eliminating any duplicates. If one of the functions returned a void type, no type was added to the list. I stored this result and later on did
exactly things
with it (such as display it).
What this meant in practice was that I was sometimes left with 0-element and 1-element variants. It would be nice if the 0 case just worked as expected rather than having to special-case. As well, in the case of a single-element variant, it would be nice if the ID weren't explicitly stored since it would always be the same value (I don't remember if Boost.Variant makes this optimization). If there is some ambiguity with how a 0-element variant should be handled, then I'd say perhaps we should leave it as a compile-time error, but I think that in practice you'd just expect it to be a stateless type that either always does nothing when visited, passes some kind of special tag type when visited (this might be better since it would work with n-ary visitation), or simply produces a compile-time error when visited. I don't know what this means with respect to Variant's "never-empty" guarantee or the result of .empty(), but intuitively I'd just say that a variant satisfies the never-empty guarantee iff the amount of types it is over is greater than 0. In the 0 case, empty() would return false (it should also be static constexpr in any case).
Such a type of "nullable" variant<> exists as
container
On Tue, Dec 10, 2013 at 8:36 AM, Matt Calabrese
On Mon, Dec 9, 2013 at 12:28 PM, Peter Dimov
wrote: Steven Watanabe wrote:
The difference between tuple<> and variant<> is that tuple<> has exactly one possible value, but variant<> has no possible values.
Not conceptually. tuple
is struct { X x; Y y; }. variant is union { X x; Y y; }. tuple<> is struct {}. variant<> is union {}. All are valid. It's not that important though. The current variant doesn't support this case, so there's not much reason for the variadic one to do so. Sorry for bringing it up.
I'm going to have to back you up here that a variant<> does sometimes make sense and I don't think we'd lose anything by supporting it. In real, non-hypohetical code, I've encountered a place where I had a 0-element variant come up and had to special-case my code so that it would work as expected. The way it came up was I had a variant of several types (the type list was the result of some metaprogramming) and a visitor of that variant that returned the result of a member function call on the currently stored object, whichever one it was (each type in the variant had the same named function but the return type wasn't necessarily the same). I constructed the return type based on the return types of the functions in question, eliminating any duplicates. If one of the functions returned a void type, no type was added to the list. I stored this result and later on did things with it (such as display it).
I think that case would better be handled by variant<blank>. I.e. you can replace void return types with blank and the rest of the code can be left intact.
On Tue, Dec 10, 2013 at 5:54 AM, Andrey Semashev
I think that case would better be handled by variant<blank>. I.e. you can replace void return types with blank and the rest of the code can be left intact.
While that is the current workaround, I look at it as exactly that -- a work-around. It just seems very arbitrary to me and only makes it so that people have to special-case for a logical variant<> in metaprogrammed code. As Peter Dimov also pointed out, variant<> seems to be just as valid as a union {}, and I think most people would expect it to work pretty much the same way. I also don't think we'd lose anything by simply supporting it and the implementation would be trivial. -- -Matt Calabrese
On Tuesday 10 December 2013 09:32:11 Matt Calabrese wrote:
On Tue, Dec 10, 2013 at 5:54 AM, Andrey Semashev
wrote: I think that case would better be handled by variant<blank>. I.e. you can replace void return types with blank and the rest of the code can be left intact.
While that is the current workaround, I look at it as exactly that -- a work-around. It just seems very arbitrary to me and only makes it so that people have to special-case for a logical variant<> in metaprogrammed code. As Peter Dimov also pointed out, variant<> seems to be just as valid as a union {}, and I think most people would expect it to work pretty much the same way. I also don't think we'd lose anything by simply supporting it and the implementation would be trivial.
The problem is that, as opposed to union, variant provides advanced APIs which loose their meaning if no type is specified. Making variant<> a special case with its own set of APIs makes generic programming more difficult since you have to propagate support for that special case through all surrounding templates. And on top of that the special case adds variant implementation complexity for little-to-no outcome. If variant<> is really desired, it has to support all the APIs that are defined now for the non-empty variant. You can achieve that by making it always non-empty with a reasonable default, e.g.: template< typename T0 = blank, typename... TN > class variant;
On Tue, Dec 10, 2013 at 10:43 AM, Andrey Semashev wrote: The problem is that, as opposed to union, variant provides advanced APIs
which
loose their meaning if no type is specified. While that's true currently, I think that the issue is just the way in
which we have specified the requirements of a variant. In other words, it
was a design choice made early on because there wasn't a clear use-case for
variant<>. IMO, a variant<> is, as Peter also pointed out, analogous to
union {}, and I don't see anything logically wrong with such a construct.
I've also encountered the issue in practice so there is at least one
use-case. Making variant<> a special case
with its own set of APIs makes generic programming more difficult since you
have to propagate support for that special case through all surrounding
templates. I'm not sure I agree. It wouldn't have its own set of APIs. At most, it
would just be a subset of variant functionality (I.E. which() might not be
defined, or perhaps better, would be declared but either not be defined or
would static_assert if the definition were instantiated). If code you're
writing doesn't need to deal with the empty variant case, none of this
should be impacting you, you just personally have the requirement that the
number of types is >= 1 in your generic code, but for people that do need
to deal with it, they'd simply be using the subset of the API that makes
sense in their generic code. At this point in time, variant<> is just a
compile-error at the library-level, so users have to special-case if some
metaprogramming happens to results in a variant<>, regardless of what
trivial things they may attempt to do with it. Directly supporting
variant<> in a way that makes sense would eliminate the need for users to
do low-level special-casing.
Again, this isn't hypothetical. In practice I have encountered uses and the
existence of a variant<> would have just worked had it existed. For
instance, in the case I described in an earlier email, I had a "display"
visitor that displayed the contained object. In the empty case it made
sense for me to just do nothing. If variant<> were defined and
apply_visitor simply passed in an instance of some null tag type, it would
have been trivial for me to accomplish that without doing any
special-casing for the creation of the variant -- my visitor could have
just been defined to do nothing in that case. I wouldn't have had to
special case the creation of the variant. Further, I don't see how this
use-case is at-all unique and I wouldn't be surprised if others have
encountered situations where metaprogramming results in a variant<>.
Manually passing in a tag type when creating the variant in the case that
metaprogramming results in an empty sequence seems like a code smell to me.
If variant<> is really desired, it has to support all the APIs that are defined now for the non-empty variant. You can achieve that by making it
always non-empty with a reasonable default, e.g.: template< typename T0 = blank, typename... TN >
class variant; Something like that may very well end up being the simplest solution and
I'd be in favor of a simple change such as that over the current state of
having variant<> be a compile error (similarly, make_variant_over with an
empty sequence should work...). I still think supporting an /actually/
empty variant might be better, but as long as there is some way to
logically represent variant<> and deal with it in generic code I'd
personally be satisfied.
--
-Matt Calabrese
On Tuesday 10 December 2013 23:13:32 Peter Dimov wrote:
template< typename T0 = blank, typename... TN > class variant;
Mapping variant<> to variant<blank> is not a good idea in principle. This has the potential of creating endless loops in generic code that takes variant
and recursively calls itself with variant .
But such code is currently not valid, it should end the recursion at variant<T>. This doesn't change after the default is added.
AMDG On 12/10/2013 06:57 PM, Andrey Semashev wrote:
On Tuesday 10 December 2013 23:13:32 Peter Dimov wrote:
template< typename T0 = blank, typename... TN > class variant;
Mapping variant<> to variant<blank> is not a good idea in principle. This has the potential of creating endless loops in generic code that takes variant
and recursively calls itself with variant . But such code is currently not valid, it should end the recursion at variant<T>. This doesn't change after the default is added.
So, anyone writing such a loop has to treat variant<> as a special case. That doesn't seem like the greatest idea. In Christ, Steven Watanabe
On Wed, Dec 11, 2013 at 8:30 AM, Steven Watanabe
AMDG
On 12/10/2013 06:57 PM, Andrey Semashev wrote:
On Tuesday 10 December 2013 23:13:32 Peter Dimov wrote:
template< typename T0 = blank, typename... TN > class variant;
Mapping variant<> to variant<blank> is not a good idea in principle. This has the potential of creating endless loops in generic code that takes variant
and recursively calls itself with variant . But such code is currently not valid, it should end the recursion at variant<T>. This doesn't change after the default is added.
So, anyone writing such a loop has to treat variant<> as a special case. That doesn't seem like the greatest idea.
Ok, we can allow variant<> in recursion but still make it act like variant<blank>.
On 12/10/13 14:32, Matt Calabrese wrote:
On Tue, Dec 10, 2013 at 10:43 AM, Andrey Semashev
wrote: [snip]
Making variant<> a special case with its own set of APIs makes generic programming more difficult since you have to propagate support for that special case through all surrounding templates.
I'm not sure I agree. It wouldn't have its own set of APIs. At most, it would just be a subset of variant functionality (I.E. which() might not be defined, or perhaps better, would be declared but either not be defined or would static_assert if the definition were instantiated). If code you're writing doesn't need to deal with the empty variant case, none of this should be impacting you, you just personally have the requirement that the number of types is >= 1 in your generic code, but for people that do need to deal with it, they'd simply be using the subset of the API that makes sense in their generic code. At this point in time, variant<> is just a compile-error at the library-level, so users have to special-case if some metaprogramming happens to results in a variant<>, regardless of what trivial things they may attempt to do with it. Directly supporting variant<> in a way that makes sense would eliminate the need for users to do low-level special-casing.
Again, this isn't hypothetical. In practice I have encountered uses and the existence of a variant<> would have just worked had it existed. For instance, in the case I described in an earlier email, I had a "display" visitor that displayed the contained object. In the empty case it made sense for me to just do nothing. If variant<> were defined and apply_visitor simply passed in an instance of some null tag type, it would have been trivial for me to accomplish that without doing any special-casing for the creation of the variant -- my visitor could have just been defined to do nothing in that case. I wouldn't have had to special case the creation of the variant. Further, I don't see how this use-case is at-all unique and I wouldn't be surprised if others have encountered situations where metaprogramming results in a variant<>. Manually passing in a tag type when creating the variant in the case that metaprogramming results in an empty sequence seems like a code smell to me.
[snip] Hi Matt, I'm trying to understand how you arrive at the static_visitor. Since the set of bounded types in the variant is a result of metaprogramming, I'm guessing that the static_visitor would also have to be the result of some metaprogramming. Otherwise I can't figure out how it could be done. I'm guessing that you use some sort of inheritance for each bounded type. Is that right? -regards, Larry
On 12/10/13 15:35, Larry Evans wrote: [snip]
Hi Matt,
I'm trying to understand how you arrive at the static_visitor. Since the set of bounded types in the variant is a result of metaprogramming, I'm guessing that the static_visitor would also have to be the result of some metaprogramming. Otherwise I can't figure out how it could be done. I'm guessing that you use some sort of inheritance for each bounded type.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Should be:
inheritance for the operator() member function
for each bounded type.
IOW.
template
Is that right?
-regards, Larry
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On Tue, Dec 10, 2013 at 1:35 PM, Larry Evans
I'm trying to understand how you arrive at the static_visitor. Since the set of bounded types in the variant is a result of metaprogramming, I'm guessing that the static_visitor would also have to be the result of some metaprogramming. Otherwise I can't figure out how it could be done.
The visitor is not metaprogrammed. The "display" visitor is used for generic visualization of the variant, regardless of what's inside. It simply has an operator() template that uses a display function found by ADL (much like operator<< would be if we were doing generic output to a stream). If the variant is just variant<>, it makes sense to not display anything in this particular case. This would all be trivial to do if variant<> just worked and we specified what happens when you use apply_visitor on it.
I'm guessing that you use some sort of inheritance for each bounded type. Is that right?
No. -- -Matt Calabrese
participants (6)
-
Andrey Semashev
-
Antony Polukhin
-
Larry Evans
-
Matt Calabrese
-
Peter Dimov
-
Steven Watanabe