What are good use-cases for tuples in C++11?

32,579

Solution 1

Well, imho, the most important part is generic code. Writing generic code that works on all kinds of structs is a lot harder than writing generics that work on tuples. For example, the std::tie function you mentioned yourself would be very nearly impossible to make for structs.

this allows you to do things like this:

  • Store function parameters for delayed execution (e.g. this question )
  • Return multiple parameters without cumbersome (un)packing with std::tie
  • Combine (not equal-typed) data sets (e.g. from parallel execution), it can be done as simply as std::tuple_cat.

The thing is, it does not stop with these uses, people can expand on this list and write generic functionality based on tuples that is much harder to do with structs. Who knows, maybe tomorrow someone finds a brilliant use for serialization purposes.

Solution 2

It is an easy way to return multiple values from a function;

std::tuple<int,int> fun();

The result values can be used elegantly as follows:

int a;
int b;
std::tie(a,b)=fun();

Solution 3

I think most use for tuples comes from std::tie:

bool MyStruct::operator<(MyStruct const &o) const
{
    return std::tie(a, b, c) < std::tie(o.a, o.b, o.c);
}

Along with many other examples in the answers here. I find this example to be the most commonly useful, however, as it saves a lot of effort from how it used to be in C++03.

Solution 4

I think there is NO good use for tuples outside of implementation details of some generic library feature.

The (possible) saving in typing do not offset the losses in self-documenting properties of the resulting code.

Substituting tuples for structs that just takes away a meaningful name for a field, replacing the field name with a "number" (just like the ill-conceived concept of an std::pair).

Returning multiple values using tuples is much less self-documenting then the alternatives -- returning named types or using named references. Without this self-documenting, it is easy to confuse the order of the returned values, if they are mutually convertible.

Solution 5

Have you ever used std::pair? Many of the places you'd use std::tuple are similar, but not restricted to exactly two values.

The disadvantages you list for tuples also apply to std::pair, sometimes you want a more expressive type with better names for its members than first and second, but sometimes you don't need that. The same applies to tuples.

Share:
32,579

Related videos on Youtube

zvrba
Author by

zvrba

Updated on July 09, 2022

Comments

  • zvrba
    zvrba almost 2 years

    What are good use-cases for using tuples in C++11? For example, I have a function that defines a local struct as follows:

    template<typename T, typename CmpF, typename LessF>
    void mwquicksort(T *pT, int nitem, const int M, CmpF cmp, LessF less)
    {
      struct SI
      {
        int l, r, w;
        SI() {}
        SI(int _l, int _r, int _w) : l(_l), r(_r), w(_w) {}
      } stack[40];
    
      // etc
    

    I was considering to replace the SI struct with an std::tuple<int,int,int>, which is a far shorter declaration with convenient constructors and operators already predefined, but with the following disadvantages:

    • Tuple elements are hidden in obscure, implementation-defined structs. Even though Visual studio interprets and shows their contents nicely, I still can't put conditional breakpoints that depend on value of tuple elements.
    • Accessing individual tuple fields (get<0>(some_tuple)) is far more verbose than accessing struct elements (s.l).
    • Accessing fields by name is far more informative (and shorter!) than by numeric index.

    The last two points are somewhat addressed by the tie function. Given these disadvantages, what would be a good use-case for tuples?

    UPDATE Turns out that VS2010 SP1 debugger cannot show the contents of the following array std::tuple<int, int, int> stack[40], but it works fine when it's coded with a struct. So the decision is basically a no-brainer: if you'll ever have to inspect its values, use a struct [esp. important with debuggers like GDB].

    • KillianDS
      KillianDS about 12 years
      the indexing problem can be solved with proper defined consts/enums.
    • Puppy
      Puppy about 12 years
      You wrote a sort function that takes T*, size? lolwot, why would you ever do such a thing.
    • zvrba
      zvrba about 12 years
      @DeadMG Just so that I can give you some material for trolling.
    • ildjarn
      ildjarn about 12 years
      @zvrba : He's not trolling (or maybe he is, but), he's right.
    • zvrba
      zvrba about 12 years
      @ildjarn Right about what, he didn't even write a statement [something that could be judged as true or false]?
    • ildjarn
      ildjarn about 12 years
      @zvrba : The implied (obvious) statement is that you shouldn't be using raw C-arrays.
    • zvrba
      zvrba about 12 years
      gosh, yet another brainwashed dogmatic C++ "programmer" who should better turn to Java instead. C++ has the philosophy of not paying for what you're not using, and I'm definitely not going to pretend that I have written a "generic" algorithm that works only on random access iterators, and works well only on in-memory data. FYI, the underlying data structure in the main program is a vector (I'm not a masochist, so I don't want to fiddle with allocations manually) and the pointer comes from &vec[0].
    • Nicol Bolas
      Nicol Bolas about 12 years
      This is a list question. You're asking for a list of things that fit some arbitrary criteria of what you consider to be "good". This is not a good question for Stack Overflow.
    • ildjarn
      ildjarn about 12 years
      "FYI, the underlying data structure in the main program is a vector (I'm not a masochist, so I don't want to fiddle with allocations manually) and the pointer comes from &vec[0]." So pass a std::vector<T>& rather than a T* and make the API more sensible. (BTW, being aggressive towards the people you expect to help you is more than slightly stupid.)
    • zvrba
      zvrba about 12 years
      @ildjarn 1) Passing std::vector would prevent the function from working with plain arrays. 2) The question was about use-cases for tuples in C++, not soliciting comments about coding style.
    • zvrba
      zvrba about 12 years
      @NicolBolas I don't agree that it's a "list question", but I'm going to vote to close it as the "Related" links show an almost exact duplicate. [Funnily, it wasn't shown while I was preparing the question.]
    • zvrba
      zvrba about 12 years
    • ildjarn
      ildjarn about 12 years
      @zvrba : Comments are for giving unsolicited.. comments. Hence the distinction between comments and answers. ;-]
    • zvrba
      zvrba about 12 years
      @ildjarn All well and fine, but comments with no relevance whatsoever to the actual question are not "helping", so you should be prepared to get some hostility back.
    • ildjarn
      ildjarn about 12 years
      I was defending @DeadMG, hostility is more than welcome. ;-D
    • Puppy
      Puppy about 12 years
      @zvrba: Deques and circular buffers, amongst other structures, also offer good random access. More relevantly, the cost of using good style is in this case virtually nothing, as a random iterator pair has an extremely similar interface. Why would you throw away being more generic for no benefit whatsoever? In addition, someone else's idea of "working well" may well be totally different to yours.
    • zvrba
      zvrba about 12 years
      @DeadMG The signature reflects that the algorithm is as generic as possible while still adhering to the underlying machine model for which it is designed and coded. If it were simple to get the pointer (not the iterator) to the last element of the vector, the interface would have taken a pair of pointers. But I deem that v.size() is nicer from the end-user perspective than &v[v.size()], and the latter will most probably trigger a debug assert. And length is int as unsigned sizes used in STL interfaces are a pile of crap.
    • GManNickG
      GManNickG about 12 years
      @zvrba: Just take two generic iterators. Pass v.begin() and v.end(), or std::begin(arr) and std::end(arr). Done.
  • KillianDS
    KillianDS about 12 years
    @zvrba: that's "compile" time interop, runtime interop is also possible. Also, another language can use C++ interfaces. I bet things like boost.python could benefit from this.
  • John Zwinck
    John Zwinck about 12 years
    @zvrba: C++ interoperates with .NET, Python, Lua, Ruby, Perl, Fortran, etc. Interoperate here means being able to link and call functions...and if you're going to write glue libraries like Boost Python, Luabind, Rcpp, etc., it's nice to have features like tuples available as part of the language vocabulary to make the interop appear as seamless as possible.
  • zvrba
    zvrba about 12 years
    @JohnZwinck "Interoperate" means to me being to call functions in other languages without writing glue layers. So that encompasses C, C++, and Fortran, which I have forgotten. But I agree it's nice to have tuples which allow to more closely mirror the "other language's" API.
  • zvrba
    zvrba about 12 years
    Marked as answer since you've made me aware of tuple_cat, which wasn't listed in MSDN for VS2010. But how would you declare the return value of a function that returns tuple_cat(x,y) for some tuples x and y whose types are not easily deducible from function arguments?
  • underscore_d
    underscore_d over 8 years
    Yeah, I just found out about and made use of this today. I used a slightly more elaborate/generic method, which I might flesh out into another answer.
  • underscore_d
    underscore_d over 7 years
    standard objection: Wouldn't struct MyFuncReturn { MyClass output1, output2; }; MyFuncReturn my_func(MyClass const& input); be superior? If we're talking "semantic strength", then a purpose-built struct has far more of that than any tuple. Sure, it parallels the existing meaning of function arguments - where only order matters, not names - which might suffice for you. However, the same argument - semantic meaning - is why people are constantly trying to find a way to make named arguments work in languages like this, without introducing total breakage/brittleness. They'd laugh at tuple.
  • Chris Drew
    Chris Drew over 7 years
    I'm interested in your first use case for tuple, wouldn't it be easier to store function parameters using std::bind than to use a tuple? This was asked on the question you linked to but the answer was just that someone else was giving them the tuple.
  • anton_rh
    anton_rh over 6 years
    Passing arguments by order isn't much better than returning multiple values using tuple. You still can confuse the order of parameters. But at least each parameter has its own name. Tuple's values do not.
  • anton_rh
    anton_rh over 6 years
    You can't use auto. You can't use copy constructors (and so can't use RVO). If it were possible to write std::tie(auto a, auto b)=fun(); it would make sense. You can write auto tuple = func(); and then use std::get<0>(tuple); and std::get<1>(tuple); but that's awful.
  • underscore_d
    underscore_d about 5 years
    @anton_rh Structured bindings already arrived in C++17 and solve your concerns.