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

9

49

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

Avignon answered 21/4, 2012 at 13:31 Comment(18)
the indexing problem can be solved with proper defined consts/enums.Titus
You wrote a sort function that takes T*, size? lolwot, why would you ever do such a thing.Marquet
@DeadMG Just so that I can give you some material for trolling.Avignon
@Avignon : He's not trolling (or maybe he is, but), he's right.Whoop
@Whoop Right about what, he didn't even write a statement [something that could be judged as true or false]?Avignon
@Avignon : The implied (obvious) statement is that you shouldn't be using raw C-arrays.Whoop
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].Avignon
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.Gerson
"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.)Whoop
@Whoop 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.Avignon
@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.]Avignon
possible duplicate of Boost::Tuples vs Structs for return valuesAvignon
@Avignon : Comments are for giving unsolicited.. comments. Hence the distinction between comments and answers. ;-]Whoop
@Whoop 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.Avignon
I was defending @DeadMG, hostility is more than welcome. ;-DWhoop
@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.Marquet
@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.Avignon
@zvrba: Just take two generic iterators. Pass v.begin() and v.end(), or std::begin(arr) and std::end(arr). Done.Shaquitashara
T
31

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.

Titus answered 21/4, 2012 at 14:3 Comment(2)
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?Avignon
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.Matrilineage
H
66

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();
Higa answered 21/4, 2012 at 13:48 Comment(2)
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.Blade
@Blade Structured bindings already arrived in C++17 and solve your concerns.Zayin
T
31

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.

Titus answered 21/4, 2012 at 14:3 Comment(2)
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?Avignon
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.Matrilineage
E
21

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.

Ene answered 24/4, 2013 at 16:6 Comment(1)
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.Zayin
E
15

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.

Eyewash answered 25/12, 2012 at 5:56 Comment(1)
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.Blade
G
13

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.

Guillen answered 26/5, 2012 at 16:11 Comment(0)
B
11

The real use cases are situations where you have unnameable elements- variadic templates and lambda functions. In both situations you can have unnamed elements with unknown types and thus the only way to store them is a struct with unnamed elements: std::tuple. In every other situation you have a known # of name-able elements with known types and can thus use an ordinary struct, which is the superior answer 99% of the time.

For example, you should NOT use std::tuple to have "multiple returns" from ordinary functions or templates w/ a fixed number of generic inputs. Use a real structure for that. A real object is FAR more "generic" than the std::tuple cookie-cutter, because you can give a real object literally any interface. It will also give you much more type safety and flexibility in public libraries.

Just compare these 2 class member functions:

std::tuple<double, double, double>  GetLocation() const; // x, y, z

GeoCoordinate  GetLocation() const;

With a real 'geo coordinate' object I can provide an operator bool() that returns false if the parent object had no location. Via its APIs users could get the x,y,z locations. But here's the big thing- if I decide to make GeoCoordinate 4D by adding a time field in 6 months, current users's code won't break. I cannot do that with the std::tuple version.

Bowyer answered 26/1, 2014 at 1:41 Comment(0)
M
3

I cannot comment on mirk's answer, so I'll have to give a separate answer:

I think tuples were added to the standard also to allow for functional style programming. As an example, while code like

void my_func(const MyClass& input, MyClass& output1, MyClass& output2, MyClass& output3)
{
   // whatever
}

is ubiquitous in traditional C++, because it is the only way to have multiple objects returned by a function, this is an abomination for functional programming. Now you may write

tuple<MyClass, MyClass, MyClass> my_func(const MyClass& input)
{
   // whatever
   return tuple<MyClass, MyClass, MyClass>(output1, output2, output3);
}

Thus having the chance to avoid side effects and mutability, to allow for pipelining, and, at the same time, to preserve the semantic strength of your function.

Melena answered 16/6, 2016 at 21:43 Comment(1)
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.Zayin
C
2

Interoperation with other programming languages that use tuples, and returning multiple values without having the caller have to understand any extra types. Those are the first two that come to my mind.

Ceremonial answered 21/4, 2012 at 13:35 Comment(3)
@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.Titus
@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.Ceremonial
@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.Avignon
E
0

F.21: To return multiple "out" values, prefer returning a struct or tuple.

Prefer using a named struct where there are semantics to the returned value. Otherwise, a nameless tuple is useful in generic code.

For instance, if returned values are value from the input stream and the error code, these values will not ego far together. They are not related enough to justify a dedicated structure to hold both. Differently, x and y pair would rather have a structure like Point.

The source I reference is maintained by Bjarne Stroustrup, Herb Sutter so I think somewhat trustworthy.

Erato answered 11/8, 2020 at 17:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.