5 ms·
C++11 Tuple Implementation Details (2012)
- saurik 6y agoI really hate the C++11 tuple implementation as I have never figured out a way to construct it from a recursive template algorithm. I end up giving up and letting the whole thing first default construct, at which point I assign into its fields; but I feel like a "high quality" data structure that understood the rest of the language would have given me at least an iterative way to construct the fields in order, wherein if I fail to provide an r-value to copy construct an element the whole thing gets rolled back (picture an internal iterator pattern giving me linear construction as if I were in some monad). Like, I appreciate in some cases I can do this using parameter pack expansion, but since execution of function arguments doesn't have defined order it doesn't really make sense for most of my use cases.
- Kranar 6y ago>I really hate the C++11 tuple implementation as I have never figured out a way to construct it from a recursive template algorithm. Use a combination of std::forward_as_tuple [1] and std::tuple_cat [2] to successively construct each element one-by-one and in place. If you have a specific use case I could show you how to use them. [1] https://en.cppreference.com/w/cpp/utility/tuple/forward_as_tuple https://en.cppreference.com/w/cpp/utility/tuple/forward_as_t... [2] https://en.cppreference.com/w/cpp/utility/tuple/tuple_cat https://en.cppreference.com/w/cpp/utility/tuple/tuple_cat
- comex 6y agoNot saurik, but that would involve a whole lot of redundant move constructions (to go from each tuple to the next one) which may or may not be optimized away. But I think the same problem would apply to any class: you can't construct an object without constructing its fields. The workaround would be to wrap each field in a union so that it can be trivially default constructed, then use placement new. But then where would you track how many fields have been initialized so far? In a separate iterator object? What would the tuple's destructor do?
- Lvl999Noob 6y agoIn that case, isn't this the use case for the Builder Pattern? Use a separate type that knows how to work with partially initialized tuples and correctly destructs them.
- saurik 6y agoCorrect. The implementation I had in mind when I said "internal iterator"--and which I think I've coded before back almost two decades ago--is that the tuple one-by-one in-order does a placement new on each of its elements, calling a templated callback you provide which returns the element (so you get RVO through to the placement new); in the case of an exception, it knows which it has already done by code (not by state) and so can deconstruct the appropriate objects.
- Kranar 6y agoYou use the std::forward_as_tuple to eliminate all moves. The std::forward_as_tuple is used to collect arguments to each constructor so that at the end you can build the final tuple in place.
- comex 6y agoSo you're saying that you'd build a tuple (or some other structure) of tuples of references to construction arguments. You'd still be constructing a series of successively larger objects from smaller ones, but those objects are made up of a bunch of references, and references are trivially copyable from the implementation's perspective (though not from the standard's). So those constructions would likely be optimized away via memcpy elimination, or, even if they weren't, they'd be relatively cheap. Am I understanding correctly? I suppose that works, as long as you can determine all the construction arguments in advance. It wouldn't work if, e.g., later elements of the tuple are constructed with pointers into earlier elements, so that you'd need the earlier elements to already be constructed before computing the construction arguments for the later elements.
- Kranar 6y ago
- pjmlp 6y agoAgree, but with C++20 around the corner, most platforms now using C++17 as the default for their toolchains, is the C++11 implementation something to still worry about other than niche use cases?
- yakubin 6y agoBig projects move slowly to newer standards. E.g.[1]: > Chromium targets C++14; C++17 support is not expected before mid-2021. [1]: <https://chromium.googlesource.com/chromium/src/+/master/styleguide/c++/c++.md https://chromium.googlesource.com/chromium/src/+/master/styl...>
- pjmlp 6y agoI wouldn't consider Chromium a good example though, given how Google considers most of modern C++ not welcomed.
- hvdijk 6y agoChromium is the reason a patch I had recently submitted to LLVM to fix a conformance issue in their preprocessor was reverted: it broke Chromium, because Chromium doesn't just require a preprocessor that offers guarantees in areas the standard doesn't, it specifically requires a preprocessor that does not conform to the C++ standard. No idea on whether they're putting in the work on fixing their code to at least make it valid for some version of C++.
- DoofusOfDeath 6y agoThanks for reporting this. It's the first evidence I've heard of someone (Google?) abusing their power with LLVM in order to protect Google's products. Mind sharing a link to your submission's discussion thread(s)?
- hvdijk 6y agoI don't think it's productive to speak of the LLVM maintainer's decision as an abuse of power. The actual revert happened in contradiction of documented LLVM policies (which, according to the maintainer, do not match actual LLVM policies), so I did take issue with that, but the maintainer wanted to first make sure clang had a different conforming extension that Chromium could use instead before removing the non-conforming extension, and assuming that a non-Google project would be given the same treatment -- and I have no reason to assume otherwise -- I think that is defensible. The change was <https://reviews.llvm.org/D91913 https://reviews.llvm.org/D91913>, addressing a conflict between GCC's ,##__VA_ARGS__ extension with the C and C++ standards in the corner case where a macro, let's call it FOO, takes no named parameters and is invoked as FOO(). In standards-conforming C modes, clang already acts as required by the standard, but in meant-to-be-conforming C++ modes, it does not.
- CJefferson 6y agoImplementing C++03 Tuple was awful / hilarious. I wrote the tuple implementation which was included in g++ for a while (technically libstdc++) -- it used some reasonably awful preprocessor to generate the different arities of tuple. Once you get to C++11, you at least didn't need to do it with pre-processor magic.
- raphaelj 6y agoC++ makes simple concepts so complex some time. I can't describe how disappointed I was when I used `std::variant` for the first time.
- gpderetta 6y agoAt some point in the future hopefully we will have tuples, variants and pattern matching as core language, in the meantime it is nice that the language is powerful enough to allow implementing them as a library. That adds complexity of course and it is going to be rough on the edges. At some point there were multiple lambda libraries before we got language support for it.
- FrozenVoid 6y agoa modern tuple would be std::vector<std::any> except std::any doesn't play nice with the rest of C++ ecosystem, such as iostream.
- quietbritishjim 6y agostd::vector and std::any both allocate - compared to (say) std::tuple<int, float> that's a ludicrously heavyweight solution.
- FrozenVoid 6y agostruct{} is even more efficient, but vector<any> allows to prototype with anything.Its a more genuine 'tuple object' than std::tuple.
- arc-in-space 6y agoHuh? That's not a tuple at all! I don't understand why you say this so I'm not even sure if I should point out the obvious differences. To wit, I've literally never used vector<any>, but tuple quite a lot(though often just because it's convenient for some metaprogramming hacks).
- FrozenVoid 6y agohttp://www.goldsborough.me/cpp/2018/05/22/00-32-43-type_erasure_for_unopinionated_interfaces_in_c++/ http://www.goldsborough.me/cpp/2018/05/22/00-32-43-type_eras... https://www.oreilly.com/content/c17-upgrades-you-should-be-using-in-your-code/ https://www.oreilly.com/content/c17-upgrades-you-should-be-u...
- arc-in-space 6y agoI hope dang forgives me my tone, and my compulsion to respond to everything. I'm getting the sense you have no idea what's being talked about here, and what tuple is actually for. Instead of saying anything at all you give me two links, one of which is a 30 minute read(are you serious?) that seems to have little to do with the topic, and the other is literally just an explanation of what std::any is(really, are you serious?). I'll try to be at least somewhat charitable, and explain where I'm coming from. Tuple is a statically-typed container, that is useful in a generic context - which, crucially, doesn't mean polymorphic at runtime! - because its members' types can be inspected at compile time, unlike a struct. This makes it useful for implementing all sorts of template magic. You can also use it like a anonymous struct, for a quick-and-dirty way to return multiple values from a function, though a struct is often better. std::any, on the other hand, is, uh, not like that at all, whether it is in a vector or not. It holds a single value that is polymorphic at runtime, by erasing its type, which has nothing to do with tuples, and helps with none of the above.
- isoblvck 6y agoa post from almost a decide ago? why do I care it's what c++20 now.