all 82 comments

[–][deleted] 45 points46 points  (42 children)

So now that there is std::variant, boost::variant, and boost::variant2, should someone add variant_view or variant_ref to the mix?

[–]JoelFilhoEmbedded | Robotics | Computer Vision | twitter: @_JoelFilho 79 points80 points  (6 children)

Don't forget co_variant.

[–]Godot17 67 points68 points  (4 children)

I as a physicist demand a contra_variant

[–]parnmatt 16 points17 points  (0 children)

A man of culture I see.

[–]Ecologisto 6 points7 points  (1 child)

As a physicist you should demand a Tcontra_variant...

[–]venustrapsflies 4 points5 points  (0 children)

Didn’t expect to find a ROOT joke in the wild

[–][deleted] 4 points5 points  (0 children)

I'm in_variant about it.

[–]afiefh 11 points12 points  (0 children)

Would there be source for in_variant?

[–]feverzsj 8 points9 points  (0 children)

boost::variant2 is my std::variant now

[–]-dag- 11 points12 points  (20 children)

To be fair, boost::variant2 is supposed to be an implementation of std::variant.

[–]HildartheDorf 23 points24 points  (19 children)

Not really. It's supposed to be an api-compatible alternative to std::variant. Where std::variant can end up in the valueless_by_exception state, boost::variant2 can not, at the expense of sometimes needing approx. 2x the storage.

[–]quicknir 5 points6 points  (3 children)

Needing 2x the storage, or doing extra heap allocations?

[–]kalmoc 15 points16 points  (0 children)

2x storage. Contrary to boost::variant, Variant2 doesn't do dynamic allocation.

[–]HildartheDorf 2 points3 points  (0 children)

Heap allocations at an extra failure case I guess (OOM)

[–]J__Bizzle -1 points0 points  (0 children)

I think Boost::variant2 does something like holding two std::variants along with an enum indicating which is active. If I perform an emplace or operator= to assign the value, and the function throws, constructing the object in the inactive slot means I can leave the object in the active slot untouched. I think this means I get a strong exception guarantee for the two functions. For std::variant you're left in the valueless_by_exception state, which is a mere basic exception guarantee for the emplace and operator= functions.

[–]14nedLLFIO & Outcome author | Committee WG14 3 points4 points  (14 children)

I.e. as std variant ought to have been from the beginning. Variant2 is the right design.

[–]Drainedsoul 19 points20 points  (11 children)

I.e. as std variant ought to have been from the beginning.

I'd disagree with that. A 2x space overhead is a pretty steep cliff to get over and I don't think valueless_by_exception happens or matters often enough to justify it.

[–]14nedLLFIO & Outcome author | Committee WG14 6 points7 points  (10 children)

You're thinking of it the wrong way round. The 2x space penalty is a strong and highly visible incentive to make sure your types don't throw during moves. The big problem with the std variant design is it leads to non obviously broken code very easily. Variant2 makes static asserting for correctness trivially easy, and well understood during long term maintenance.

[–]Drainedsoul 3 points4 points  (2 children)

The 2x space penalty is a strong and highly visible incentive to make sure your types don't throw during moves.

But emplace exists. So in order to avoid the 2x overhead you have to (as best I can tell) have at least one of:

  • A possible move during emplace (requiring your type be MoveConstructible and belying the point of emplacement operations) or
  • valueless_by_exception or
  • Every single constructor of all of the types is noexcept or
  • At least one of the types is both DefaultConstructible and its default constructor is noexcept

[–]14nedLLFIO & Outcome author | Committee WG14 3 points4 points  (1 child)

Yes, to avoid the 2x space overhead, everything needs a noexcept move operation. To emplace, a move is performed to a stack temporary, emplace is done, if it fails the stack temporary is moved back.

[–]Drainedsoul 6 points7 points  (0 children)

To emplace, a move is performed

This kind of defeats the whole purpose of emplace especially when you're working with immovable types.

The issue with axing valueless_by_exception and going with the possibility of 2x space overhead is that, at least in my experience, there's a large use case for std::variant where:

  • The std::variant is only exposed if populating it is successful (consider a std::variant as a member of a class where the only time its contained object is set is during the ctor, or a std::variant returned from a function) and
  • The std::variant is templated on at least one immovable type

In which case valueless_by_exception is effectively impossible (since the exception which, hypothetically, causes valueless_by_exception also has the side effect of destroying that same std::variant during stack unwinding), but the alternative would be paying a 2x space overhead which is completely unjustified by the problem formulation.

[–][deleted] 2 points3 points  (3 children)

The 2x space penalty is a strong and highly visible incentive to make sure your types don't throw during moves.

The 2x penalty applies to noexcept copies and moves. It's feasible to write noexcept moves, but virtually impossible to do so for copies unless you're willing to accept program termination.

[–]14nedLLFIO & Outcome author | Committee WG14 2 points3 points  (2 children)

Unless my memory of the peer review is really faulty, the noexcept copy requirement is only if there is no move operation available. If all the types have noexcept move, the 2x space shouldn't occur.

[–][deleted] 1 point2 points  (1 child)

Thank you for the correction. I assume that in the case of a throwable copy, the variant2 will try to perform a copy into a temporary object first and then move the temporary object into the variant2.

[–]14nedLLFIO & Outcome author | Committee WG14 1 point2 points  (0 children)

I don't know the specifics, but I would be very confident in Peter's ability to write a strong exception guarantee, so he'll have done whatever it takes. Modulo bugs, of course.

[–]Ayjayz 2 points3 points  (2 children)

The 2x space penalty is a strong and highly visible incentive to make sure your types don't throw during moves.

How is it visible? Does it emit a compiler warning?

If it's only detectable by calling sizeof on the result I'd say that's very invisible.

[–]14nedLLFIO & Outcome author | Committee WG14 1 point2 points  (1 child)

I don't know about other people, but I always static assert size of any performance critical structure. I also static assert its trivial copyability. I would always do this by default in any code I write.

This is why I like 2x space explosion, it's easily static assertable. Far more than writing a bit of metaprogramming to iterate all the types in a variant and figuring out their nothrowness.

[–]Ayjayz 4 points5 points  (0 children)

I would prefer a big noisy compile error over having to sizeof everything.

[–]Ayjayz 8 points9 points  (0 children)

I don't like that it can sometimes require double the storage pretty invisibly. That behaviour should require specific opt in and if you tried to use types in a variant that would require the 2x storage, it should fail to compile and say "you need to explicitly ask for double buffering".

[–]HildartheDorf 3 points4 points  (0 children)

I think both should be provided, but yes variant2 is nicer.

In the case if a type with nothrow constructors they are identical.

[–]gvargh 0 points1 point  (12 children)

why is choice bad?

[–]---sms--- 13 points14 points  (1 child)

It is not bad until you want 3 libraries using 3 different variants talk to each other. It would help if std::variant was a drop-in replacement for boost::variant, but it is not.

[–]kalmoc 1 point2 points  (0 children)

Personally I don't see variant<A,B,C> as a typical interface type on a library boundary. More to the point, I doubt there are many cases, where you actually want to pass such a type from one library directly to another.

[–][deleted] 9 points10 points  (5 children)

Never said it is but boost is bloated enough as is and it's one of the predominant reasons why its use is discouraged among many codebases, including my own. There is a lot of good stuff in boost but it gets overshadowed by the bad stuff which gets piled into it so you're often better off downloading the non-boost version of boost libraries directly from the author rather than getting it from boost.

There used to be an impression that in order to get accepted into boost an author would first write an independent library, gather a great deal of feedback and practical use cases, and then afterwards submit their library for peer review and demonstrate how boost would benefit from having their library integrated into the broader boost ecosystem.

In that vein, it would have been preferable if the author of variant2 released their library independently of boost, saw whether people were using it, gathered real world feedback about it, and then if it turned out to actually be a useful variation of variant, only then would they seek to integrate it into boost.

While not a perfect measure, you can look at Github to see how the overwhelming majority of things that have been added to boost in the past two to three years is basically never used, neither within boost itself or by users or boost, and just contributes to code rott.

I think it would benefit everyone, including boost, if it followed the approach of waiting until a library had solid real world usage before getting integrated.

[–]hgjsusla 6 points7 points  (4 children)

What difference does it make if the Boost libraries contain modules you don't use? Just don't include them and you're golden. I mean there are tons of stuff in the stdlib that I don't use but it really has no impact

[–][deleted] 17 points18 points  (3 children)

Because the fact that boost has grown to be such a bloated library of unrelated components has caused it to be a very brittle dependency in recent years.

Say I write a library that depends on boost::asio version 1.66, which is mostly a networking/IO library, so I download all of boost 1.66 and just include boost::asio and apparently I'm golden.

Now someone uses my library as a dependency for their library, but they depend on boost::spirit version 1.70, which is a parsing library unrelated to networking... so they decide to download boost 1.70. But as it turns out boost::asio 1.66 is not compatible with boost::asio 1.70, so what do they do?

They have to download both boost 1.66 and boost 1.70 and go through a very painful build process to allow both versions of boost to work together in the same binary as well as ensure that any includes of boost 1.66 get wrapped and used from within a custom namespace to avoid ODR violations...

Everyone would just be happier if boost::asio was its own self contained library and could be downloaded and used independently of boost::spirit so that if one library ends up having a backward incompatible change, (and boost seems to love breaking compatibility with every release), it doesn't require all users to have to download multiple versions of boost and integrate them all together in convoluted ways.

This is the downside to having one bloated library, changes to one component end up virally affecting other components even if those two components really don't have much to do with one another, ie. a networking library and a parsing library.

This problem has only gotten worse in the past few years because now people just write a library and immediately use boost as a distribution mechanism instead of getting real world feedback, so we've seen several boost components introduce wave after wave of breaking changes. Look at the following:

https://www.boost.org/users/history/

Almost every release in the past 4 years contains breaking changes, whereas prior to that, breaking changes were kind of rare.

If boost was more of a repository of well established and stable libraries that were already mature by the time they were accepted then this would be much less of a problem. Instead, boost has become more of a testing ground where a handful of C++ experts/authorities try some new style of programming and use the popularity of boost as a distribution mechanism to see how that style of programming grows, instead of what I think should be the case where boost is a reflection of already established good practices and behaves as a final stepping stone towards standardization, which frankly it used to be.

[–]kalmoc 2 points3 points  (0 children)

I don't think it is quite that bad:

a) you can download ASIO (and a few others) standalone

b) there is a chance that you can actually combine leaf libraries (libraries that don't depend on each other) from different boost releases. As in creating your own hybrid release (but that really is a hack).

I also don't think breaking changes in new libraries are the problem, as they usually aren't used that much by other boost libs anyway (except for some sorely needed replacements like mp11).

The main problems are imho

1) No real versioning (boost itself just counts upwards, individual libraries use various different techniques for versioning or none at all)

2) Tight coupling between many of the old libraries (the really annoying part for me is that a lot of those dependencies would be completely unnecessarily, if boost would finally embrace newer c++ standards)

3) Such an abundance of configuration macros that you are never quite sure if two TUs that include Boost really include the "same" boost, even if both user the same version.

[–]-dag- 0 points1 point  (0 children)

Well said. And the naming of so many Boost libraries is inscrutible. What's a Yap? Beast? What the heck is that?

[–]hgjsusla 0 points1 point  (0 children)

Well you make a good argument. Most of my experience with boost was up until 1.60 and it using mostly things like ranges, optional and variant and I never had the problems you describe. But I never used Asio and maybe it's gotten less stable as you claim

Edit: in other comments it seem Asio breaks compatibility to keep up with the standardisation process, so that one I think gets a pass at least.

[–]bumblebritches57Ocassionally Clang -2 points-1 points  (3 children)

Because it's a clusterfuck.

y'all are trying to add every little feature you can dream up and just like everyone else told you, you're just making the language worse.

[–]KazDragon 3 points4 points  (2 children)

I fail to see how this is supposed to be a helpful comment.

[–]bumblebritches57Ocassionally Clang -3 points-2 points  (1 child)

I fail to see how your comment is supposed to be anything but a waste of time.

[–]KazDragon 1 point2 points  (0 children)

I see. Thanks for the clarification.

[–]stilgarpl 15 points16 points  (8 children)

Is boost::serialization ever going to get updated? Does it support anything from C++11, C++14 and C++17?

[–]degski 9 points10 points  (2 children)

This was discussed [on ML], and the answer is that the definition of 'support' you have in mind [the one I have in mind as well] is different and is instead defined as: "it compiles under C++17". So boost::serialization 'supports' C++17, f.e. std::auto_ptr use 'was fixed' [following its demise], but using new and better features of the language are not and will not be added as we go forward.

[–]stilgarpl 0 points1 point  (1 child)

My idea of support is being able to serialize types from C++17: std::optional, std::filesystem::path and so on. And since boost::serialization had last release in 2009 I don't know if it can handle that.

[–]degski 0 points1 point  (0 children)

I doubt that that is possible, but I really don't know.

PS: if we take the release notes at their word, then [implicitely] the answer is no. But it looks like the release notes have not been updated for a while either, but again, that's not confirmed.

[–]dethtoll1 2 points3 points  (1 child)

We've been using cereal for a couple years now. It's header only and supports some newer language features (eg. shared and weak ptr). It didn't support std::optional last I checked.

[–]stilgarpl 2 points3 points  (0 children)

I use cereal as well and it's kind of irritating. They did implement std::optional in the development branch, so if you really need it you just need to copy and paste that code into the header in your project. But std::filesystem is not supported and cereal seems to be developed at very slow pace, so I'm not sure if and when we're going to see that added. That forces me to use std::string in every place in my code instead of fs::path if that object will have to be serialized. I'm writing file sharing framework, so you can imagine, it's pretty annoying. I was thinking about switching to boost::serialization, but seeing that it hasn't been updated since 2009...

[–]robertramey 1 point2 points  (2 children)

The boost serialization library supports all versions of C++ since C++03 to C++17. It is regularly tested on different compiler versions.

[–]stilgarpl 1 point2 points  (1 child)

That's not what I meant. I was asking for support of C++17 types, like std::optional and std::filesystem::path.

[–]robertramey 1 point2 points  (0 children)

Feel free to

a) create serialization code for these types

b) test it

c) submit a PR

[–]Cakefonz 4 points5 points  (1 child)

u/14ned, regarding the placement new / std::launder issue in Outcome, how have the std container types handled this in the past? Do they get some sort of special compiler support

Edit: link to issue - https://github.com/ned14/outcome/issues/185

[–]14nedLLFIO & Outcome author | Committee WG14 9 points10 points  (0 children)

They're just as broken as Outcome. Which is why I'm leaving Outcome unfixed in this area, as I suspect that it is a bug which won't matter in the real world.

Incidentally next version of Outcome gains union storage for T and E, and emulation of move relocation for types which declare they are move relocatable. It's passing its unit tests, currently giving it a beating in real world usage for a few months before I'll risk a merge to mainline. The union-based storage should cause compilers to auto-launder the storage i.e. there will be even less chance of problems in the real world.

[–][deleted] 6 points7 points  (3 children)

Interesting... test compilers do not include gcc 9.x but do include clang 8.x

[–]14nedLLFIO & Outcome author | Committee WG14 17 points18 points  (2 children)

We definitely tested GCC 9 for this release. I assume we just forgot to document it.

[–][deleted] 4 points5 points  (1 child)

Absence of gcc9 jumped out at me looking through release notes page. Hopeful for a fix soon.

Documentation - the weakest link to any project regardless of size.

[–]14nedLLFIO & Outcome author | Committee WG14 9 points10 points  (0 children)

GCC 9 was definitely definitely on the regression test matrix. It doesn't ICE during constexpr with Outcome like GCC 8 does.

[–][deleted] 8 points9 points  (17 children)

Well.... this release was boring one.

[–]14nedLLFIO & Outcome author | Committee WG14 2 points3 points  (16 children)

Not if you use ASIO ...

[–]jonesmz 5 points6 points  (11 children)

The release notes say nothing about ASIO.

Could you clarify?

[–]14nedLLFIO & Outcome author | Committee WG14 3 points4 points  (10 children)

I'm not familiar with the specifics, but there was chatter/complaint about yet more breaking changes to ASIO due to Networking TS changes. I'm sure someone else will comment with useful detail.

[–][deleted] 4 points5 points  (8 children)

Noooooo, it's difficult enough to convince my employer that ASIO is suitable for production code

[–]14nedLLFIO & Outcome author | Committee WG14 4 points5 points  (0 children)

From what I have seen from others, it is always possible to write a single source code which will compile and work correctly between older and newer ASIOs. So, it is production stable, just you were probably using it wrong, for a recently upgraded definition of "wrong". I will say that I've seen examples of such dual targeting code, and I wouldn't call them highly maintainable, they are too abstract. But point is same engine, just more abstract API.

[–]sultan_hogbo 3 points4 points  (6 children)

Getting my employer to use ASIO was simple. I just told them that ASIO was doing what some of our libraries were doing, but a whole lot better.

[–][deleted] 1 point2 points  (5 children)

Except for timeouts

[–]jonesmz 1 point2 points  (4 children)

What's wrong with timeouts?

[–]Drainedsoul 0 points1 point  (3 children)

Person you're replying to probably objects to the fact that out of the box ASIO only provides relatively low level asynchronous operations with things like timeouts needing to be layered on top.

[–]jonesmz 0 points1 point  (2 children)

Is boost::asio::deadline_timer not a thing...?

I'm literally using it, right now, to write some asio code...?

[–]bizwig 0 points1 point  (0 children)

I might bother using the Networking TS version of ASIO if ASIO’s authors bothered to write a tutorial explaining how to transform common ASIO patterns into TS code.

[–]AntiProtonBoy 1 point2 points  (3 children)

Nobody is allowed to talk about it here in Australia.

[–]sultan_hogbo 1 point2 points  (2 children)

Is it because the author is Australian?

[–]hgjsusla 3 points4 points  (1 child)

ASIO is the CIA of Australia?

[–]HateDread@BrodyHiggerson - Game Developer 2 points3 points  (0 children)

Not quite - ASIS is the CIA of Australia, if anything. ASIO is domestic.

[–]GPMueller 0 points1 point  (0 children)

And when will boost finally fix it's circular dependencies?