back
10 comments
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);
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

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

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
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.
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!
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.

Tuples are similar to Haskell lists but on the type level, and you can use https://github.com/hugohp/tuple_ext/ which provides lots of functionality to work on tuples.
> std::tuple_size is only specialized for tuple<...>, so we must strip off the reference and possible const.

Is there some good reason why tuple_size couldn't have been made to work on const tuples?

Using apply, though interesting, looks like a hack TBH. From a practical standpoint, the (explicit) use of index_sequence seems better (and simpler).