Thanks to the variadic templates, the presence of the tuples in the
STL has become obvious.
But what about the variants? I recently had to write an implementation
of boost::variant using variadic templates. I could make it without
any difficulty. Hence, I thought the introduction of an std::variant
template in the C++0x's STL would be as obvious as std::tuple.
I've been surprised to notice that there wasn't any plan to do this.
Some may argue that the unrestricted unions will do the job, but IMHO
it's a pity to incorporate in the core language a feature that could
be delegated to a library. Small is beautiful.
Besides, we can't create a union type by using a template parameter
pack, like the following code does with boost/std::variant:
template<typename... T>
std::variant<T...> f()
{
//...
}
So, why?
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Exception-safety of operator= is a difficult issue with a stack-based
variant.
How did you solve it?
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>
I don't know what you're talking about.
Could you please send me a code that underlines this issue?
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
This:
http://www.boost.org/doc/libs/1_44_0/doc/html/variant/design.html#variant.design.never-empty.problem
might provide useful background on Mathias' question.
HTH.
-Larry
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
Since it will be C++0x, I would require all types in variant to have a
no-throw move constructor. Then it is just a straightforward copy&swap,
or a copy-destroy-move.
--
Thomas
> Since it will be C++0x, I would require all types in variant to have a
> no-throw move constructor.
A pretty hard requirement, that you cannot even detect at compile-time
(or can you?)
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
lhs = rhs;
where lhs and rhs are both std::variant<T1,T2,...Tn>, but
lhs.which() != rhs.which().
Now, to be more explicit, do you mean by copy&swap that you'd
copy rhs to temp then swap temp to lhs?
Likewise, could be more explicit about what 'copy-destroy-move'
means?
TIA.
-Larry
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
I wrote that code in my implementation:
===========================================================
template<typename T, typename... Ts>
const variant<T, Ts...>&
variant<T, Ts...>::operator=(const variant<T, Ts...>& o)
{
if(!head_) //head_ is a boost::optional<T>
{
head_ = o.head_;
tail_ = o.tail_;
}
else
{
tail_ = o.tail_;
head_ = o.head_;
}
return *this;
}
===========================================================
I did some tests and it seems to work.
But it seems too simple. I probably missed something.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
Not reliably. If I remember correctly, the noexcept operator and the
type traits are allowed to be pessimistic in this regard, that is,
noexcept(expression) or nothrow_xxx<T>::value are allowed to be false
even if the right answer would be true. So, it's a matter of QoI.
Yes. Exception safety. What happens if head_ = o.head_ throws an
exception? The variant will be in an inconsistent state.
Read about the problem here:
http://www.boost.org/doc/libs/1_44_0/doc/html/variant/design.html#variant.design.never-empty
--
Thomas
Yes. If the copy fails, both lhs and rhs are unchanged. If the copy
succeeds, there's no problem, since swap is no-throw (using no.throw move).
By swap I don't think of std::swap, but exchanging the complete content
of the variant (including the type indicator).
> Likewise, could be more explicit about what 'copy-destroy-move'
> means?
Looking at the boost docs:
http://www.boost.org/doc/libs/1_44_0/doc/html/variant/design.html#variant.design.never-empty.heap-backup-solution
""
1. Copy-construct the content of the left-hand side to the heap; [...]
2. Destroy the content of the left-hand side.
3. Copy-construct the content of the right-hand side in the
(now-empty) storage of the left-hand side.
4. In the event of failure, copy backup to the left-hand side storage.
5. In the event of success, deallocate the data pointed to by backup.
""
The heap backup is done because the copy operation to restore the backup
could also fail. In this case, the variant just uses the heap backup as
content.
With a non-throwing move, you could do instead:
1. Copy-construct rhs to a temporary.
2. Destroy the content of lhs.
3. Move-construct the temporary to lhs.
You don't need a (slow) heap allocation, because there is only one copy
that could fail.
--
Thomas
I did.
I wrote that piece of code to fulfill that guarantee.
I also wrote an exception safety test for my implementation.
Here it is: http://pastebin.com/qp9e0wJF
Here is the code of my implementation:
http://github.com/fgoujeon/scalpel/blob/master/src/scalpel/utility/variant.hpp
The test is successful. My variant is heap-based, though.
Is my test incomplete?
> Yes. Exception safety. What happens if head_ = o.head_ throws an
> exception? The variant will be in an inconsistent state.
Whatever which "head_ = o.head_" throws, it will happen before the
erasure of the LHS variant's content, won't it?
If so, it will be effectless.
--
You didn't say that. Your example used a boost::optional.
With a heap based implementation, you can first try a copy, then simply
change the pointers. Manipulating pointers never throws.
With a stack based implementation you can't do that. boost::variant
tries to embed the value in the variant struct and also to minimize the
memory footprint by sharing the buffer space (like a real union).
It gets more complicated if you do this.
>> Yes. Exception safety. What happens if head_ = o.head_ throws an
>> exception? The variant will be in an inconsistent state.
>
> Whatever which "head_ = o.head_" throws, it will happen before the
> erasure of the LHS variant's content, won't it?
In a heap based implementation: Yes.
> If so, it will be effectless.
But in this code:
{ tail_ = o.tail_;
head_ = o.head_; }
...tail_ is changed already when you try to copy head_. If that copy
throws, the variant is in an inconsistent state.
--
Thomas
Indeed, I didn't say that, sorry. I thought boost::optional was a
heap-based container, so when I changed my implementation to make it
use an std::unique_ptr instead, I thought it didn't change the way
the memory is allocated.
> boost::variant
> tries to embed the value in the variant struct and also to minimize the
> memory footprint by sharing the buffer space (like a real union).
Yes, I realized this during our conversation. An hypothetical
std::variant would for sure have to be implemented that way as well.
> > If so, it will be effectless.
>
> But in this code:
> { tail_ = o.tail_;
> head_ = o.head_; }
>
> ...tail_ is changed already when you try to copy head_. If that copy
> throws, the variant is in an inconsistent state.
I insist, it won't. At least, not with an heap-based variant:
============================================================
if(!head_)
{
if(o.head_)
head_ = std::unique_ptr<T>(new T(*o.head_)); //1
tail_ = o.tail_;
}
else
{
tail_ = o.tail_;
//erase the lhs variant's content
if(o.head_)
head_ = std::unique_ptr<T>(new T(*o.head_)); //2
else
head_.reset(); //3
}
============================================================
In any case, the first executed statement is 1 (if LHS's and RHS's
types are different) or 2 (if their types are the same).
I just hoped this method could have been applied to a stack-based
variant (even if I didn't try to). But according to what you assert,
it isn't possible. Too bad.
Anyway, that answers to my initial question.
Thank you everyone!
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>
Similarly, if one of the bounded types of the variant is
nothrow default-constructible, then such a type could be used
as a safe "fallback" type in the event of failed copy construction.
from:
http://www.boost.org/doc/libs/1_44_0/doc/html/variant
/design.html#variant.design.never-empty
suggests there may be a solution if the variant is actually like
an optional<T> except that T is a template arg pack. IOW,
more like optional<T...>. Like haskell's Maybe:
http://www.zvon.org/other/haskell/Outputprelude/Maybe_d.html
there would be one type, Nothing, indicating no T... is valid at
the moment. This could be the result if the operator= failed for
any reason. This Nothing would be the bounded type which is
nothrow default-constructible mentioned in the boost variant
reference above.
Does that sound reasonable?
[snip]
--