-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Base recursive_wrapper implementation on std::unique_ptr #89
Comments
If I understand correctly, recursive_wrapper was implemented specifically because it was faster than using unique_ptr. Building recursive_wrapper on top of unique_ptr would defeat that purpose. On the other hand if the recursive_wrapper implementation was incomplete, that's not good either @artemp can you shed some light on this? One other thing to keep in mind: The implementation of recursive_wrapper was basically taken wholesale from boost, just ported to C++11. Keeping compatibility with boost is one of our goals here. |
I always thought that the use of the |
Correct ^ . Our If we can alias |
Just reading #50 - perhaps this should go into 1.1.0 it we have a consensus ? |
👍 - and as for throwing destructors, they're such a dark matter that I probably shouldn't have added any |
Could we keep the discussion in this issue to the question it was started with? It is hard enough to keep track of all the different problems we have as it is. @artemp what do you refer to in the sentence "perhaps this should go into 1.1.0"? |
@joto - I referred to implementing recursive_wrapper as an alias to or based on |
I think The difference has to do with how move works and what the empty state is. A A If I guess the other reason I would advocate against basing things on |
@cbeck88 In the starting comment I gave the link to possible implementation of BTW I sure |
@tomilov: Yeah, I mostly agree with you. But I think it's different for library code perhaps. If I only have one explicit I wish that they had put Re never-empty-guarantee, I guess I just wrote a documentation section about this in my lib :p so it is fresh in my mind. IMO it's not very good to say "oh, the variant isn't empty, it merely contains a container that is empty" and give the user UB by transparently dereferencing null. If you want to document "hey user, you get UB if you visit the variant after it is moved from" that's up to you, but it also introduces other holes in the implementation -- what if we assign to the variant afterwards, to bring it back to life? It means, whenever we manipulate Re: preprocessed source complexity: On my machine, this program
gives me 3225 line count with
And Granted, that preprocessed source lines is definitely not the only thing that affects compile times. Edit With this program:
I get this line count:
|
Can we revisit this, please? One of the concerns raised against Second concern was that with
It's the other way around. Unless the documentation explicitly says an operation can be performed on a moved-from value, you should assume STL convention: such value is in an indeterminate state and should only be assigned to or destroyed. variant<int, const char*, std::string> a, b;
a = std::string("eggs");
b = std::string("bacon");
b = std::move(a);
// at this point, what `a` contains is nobody's business
// you certainly shouldn't be visiting it After the move, Now back to the cost of construction. struct Terminal {};
struct NonTerminal;
using Tree = variant<Terminal, recursive_wrapper<NonTerminal>>;
struct NonTerminal { Tree child; };
Tree grow(int n)
{
Tree t = Terminal{};
for (int i = 0; i < n; ++i) {
t = NonTerminal{ std::move(t) };
}
return t;
} How many This is really bad for bottom-up construction of tree-like variants. Such as in mapnik expression parser. |
So, here's the issue with the code example you gave, where we assume
The problem is that, However, if you put support logic for this in
So, I take back what I originally wrote -- I don't think basing The performance issue you raise here is actually extremely similar IMO to something that happens when using To cut a long story short, it is my opinion that lightmare: IMO, a state in which the variant does not believe itself to be empty, but it's still illegal to visit it, is not a valid state. But, as long as you make sure the moved-from I'm hoping at some point to get around to adding this to my variant implementation -- mine is more like |
As it stands, the moved-from state is not documented, so it could be anything. I agree that a "broken" state is not fancy, but an unspecified state isn't any more useful, and a correct program never operates on those; the moved-from variant must be assigned a new value before you use it again / pass it around, e.g.: a = no_init{}; I don't really care if the implementation is changed such that move constructor/assignment resets the moved-from variant to But I digress. I wanted to focus on |
An "unspecified but valid state" observes the basic preconditions of the object. The things you take for granted, like, it is okay to copy or move the object. Suppose you have this code
If First possibility: The variant blindly dereferences the dead pointer in Second possibility: The variant always tests if the pointer is dead before dereferencing it. But this introduces run time overhead, which you don't have when
I guess I don't agree with this. After a point this becomes semantics arguments, what is "valid", what do you consider to be the guarantees and the intended interface... but IMO only broken code has broken states like described. Unspecified state is definitely a lot more useful. |
It'll just move the (null) pointer, no need to dereference, but I see where you're going. The broken state will spread to
We're talking constant-time move and constant overhead on other operations -- setting the moved-from variant to The purpose of |
So, consider this example:
In general, all variants (that I know of), when a non-type changing assignment occurs, will not destruct and then reconstruct the value type -- instead, they use the copy assignment operator for that type, because it may be more efficient. I didn't read mapbox variant's implementation in this respect, but I assume it does the same. So, when we try to resurrect the moved-from If your So, if you allow null recursive wrapper, you need to add checks for whether or not it is null when you implement copy assignment operator of If you don't collapse all the "empty" states into one empty state, that you test for once during variant dispatch, you ultimately are going to pay a big price. I mean I don't see why you wouldn't engineer it that way. |
Yes, not destroying the stored alternative when moving from a variant is more efficient, it's the right thing to do with non-wrapped alternatives. But when moving from a #139 confirms the difference in complexity with timings, forgot to link it here. |
It is simpler to design
recursive_wrapper
as "wrapper" over ready-to-use to RAIIstd::unique_ptr
. Just add value semantics (example).The text was updated successfully, but these errors were encountered: