[HN Gopher] C++ Templates: How to Iterate through std:tuple: std...
___________________________________________________________________
C++ Templates: How to Iterate through std:tuple: std:apply and More
Author : ibobev
Score : 39 points
Date : 2022-02-14 13:57 UTC (9 hours ago)
(HTM) web link (www.cppstories.com)
(TXT) w3m dump (www.cppstories.com)
| Koshkin wrote:
| Using apply, though interesting, looks like a hack TBH. From a
| practical standpoint, the (explicit) use of index_sequence seems
| better (and simpler).
| addaon wrote:
| I feel like this is missing a relatively straightforward step
| after the the second version. The first version fails to compile
| with "couldn't infer template argument"; the second version
| corrects this at the cost of naming explicit types. The author
| then goes off in a few directions to avoid naming the types...
| but he has them right there, why not just use them?
| - std::apply(printImpl<int, int, double>, tp); +
| std::apply(printImpl<Args...>, tp);
| [deleted]
| MauranKilom wrote:
| I believe the author goes this way because the goal is a
| generic function that can iterate over tuple-like types. If you
| _know_ that you 'll work with std::tuple specifically, then you
| can indeed just deduce the contained types and call it day. But
| std::apply is more generic than that. For example, you can
| std::apply a std::pair:
|
| https://godbolt.org/z/vfcd5YoWE
|
| And the function that the author arrives at similarly supports
| other types than std::tuple:
|
| https://godbolt.org/z/hzqbMWrjq
| joebaf wrote:
| Hi, Author here - I went to lambdas because I got some
| compilation issues... because some stupid mistakes the code
| with std::apply(printImpl<Args...>, tp) didn't compile...
|
| But now I see the issue and it's improved. So the version:
|
| template <typename... Args> void printTupleApplyFn(const
| std::tuple<Args...>& tp) { std::cout << "(";
| std::apply(printImpl<Args...>, tp); // << std::cout << ")"; }
|
| is fine and probably the easiest
| addaon wrote:
| That's fair. Although it's straightforward to adopt the first
| solution for arbitrary tuple-like types as well:
| template <template <typename...> typename Wrapper,
| typename... Args> void printTupleApplyFn(const
| Wrapper<Args...>& tp) { // no further changes
| dataflow wrote:
| Keep in mind that in general that approach only works if
| you assume the template parameters are exactly the tuple
| types. Any extra parameters (or different representation)
| would make it not work. In general you don't want to use
| template template parameters because of this. It's
| analogous to depending on the physical layout of a struct
| rather than the names of its members.
| MauranKilom wrote:
| Well, the authoritative answer to "which types are
| contained in this tuple-like type" is
| std::tuple_element(_t). That's exactly what the std::apply
| machinery relies on, which is why it's pretty convenient to
| build things around that.
|
| Your suggestion fails for e.g. std::array, which is also
| tuple-like:
|
| https://godbolt.org/z/qYhdfjrY3
|
| User-defined types can also specialize the required
| templates to opt-in to tuple-like behavior. In fact, doing
| so enables structured binding support, so it's not unlikely
| to see something like Vec3 getCoords();
| auto [x, y, z] = getCoords();
|
| (where Vec3 specializes std::tuple_element etc.), and you
| wouldn't want your tuple printing code to error out when
| given a Vec3 either. In fact, Vec3 might not even be a
| template in the first place!
| addaon wrote:
| That all makes sense, I agree totally.
|
| One of the challenges with this sort of unmotivated code
| (unmotivated in a literal sense, not a derogatory sense
| -- we don't know what's motivating it) is that it's very
| hard to know how general a solution to design. I love the
| C++ template universe when things "just work," but it's
| very easy to get into a cycle where just a /bit/ more
| complexity gets you a /bit/ more generality -- only to
| realize that after beautifully polishing that modular
| code, you only use it in one or two contexts.
|
| std::apply is a good example of putting that generality
| and complexity into the standard library; if a caller can
| then use that directly, even with loss of generality,
| that's usually my preference. If nothing else, it means
| folks encountering the caller will have fewer layers to
| understand, and many fewer non-standard layers.
___________________________________________________________________
(page generated 2022-02-14 23:01 UTC)