Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are C++ tuples so weird?

I usually create custom structs when grouping values of different types together. This is usually fine, and I personally find the named member access easier to read, but I wanted to create a more general purpose API. Having used tuples extensively in other languages I wanted to return values of type std::tuple but have found them much uglier to use in C++ than in other languages.

What engineering decisions went into making element access use an integer valued template parameter for get as follows?

#include <iostream>
#include <tuple>

using namespace std;

int main()
{
    auto t = make_tuple(1.0, "Two", 3);
    cout << "(" << get<0>(t) << ", " 
                << get<1>(t) << ", " 
                << get<2>(t) << ")\n";
}

Instead of something simple like the following?

t.get(0)

or

get(t,0)

What is the advantage? I only see problems in that:

  • It looks very strange using the template parameter like that. I know that the template language is Turing complete and all that but still...
  • It makes indexing by runtime generated indices difficult (for example for a small finite ranged index I've seen code using switch statements for each possibility) or impossible if the range is too large.

Edit: I've accepted an answer. Now that I've thought about what needs to be known by the language and when it needs to be known I see it does make sense.

like image 262
DuncanACoulter Avatar asked Jul 08 '19 15:07

DuncanACoulter


2 Answers

The second you've said:

It makes indexing by runtime generated indices difficult (for example for a small finite ranged index I've seen code using switch statements for each possibility) or impossible if the range is too large.

C++ is a strongly static typed language and has to decide the involved type compile-time

So a function as

template <typename ... Ts>
auto foo (std::tuple<Ts...> const & t, std::size_t index)
 { return get(t, index); }

isn't acceptable because the returned type depends from the run-time value index.

Solution adopted: pass the index value as compile time value, so as template parameter.

As you know, I suppose, it's completely different in case of a std::array: you have a get() (the method at(), or also the operator[]) that receive a run-time index value: in std::array the value type doesn't depends from the index.

like image 153
max66 Avatar answered Oct 06 '22 15:10

max66


The "engineering decisions" for requiring a template argument in std::get<N> are located way deeper than you think. You are looking at the difference between static and dynamic type systems. I recommend reading https://en.wikipedia.org/wiki/Type_system, but here are a few key points:

  • In static typing, the type of a variable/expression must be known at compile-time. A get(int) method for std::tuple<int, std::string> cannot exist in this circumstance because the argument of get cannot be known at compile-time. On the other hand, since template arguments must be known at compile-time, using them in this context makes perfect sense.

  • C++ does also have dynamic typing in the form of polymorphic classes. These leverage run-time type information (RTTI), which comes with a performance overhead. The normal use case for std::tuple does not require dynamic typing and thus it doesn't allow for it, but C++ offers other tools for such a case.
    For example, while you can't have a std::vector that contains a mix of int and std::string, you can totally have a std::vector<Widget*> where IntWidget contains an int and StringWidget contains a std::string as long as both derive from Widget. Given, say,

    struct Widget {
       virtual ~Widget();
       virtual void print();
    };
    

    you can call print on every element of the vector without knowing its exact (dynamic) type.

like image 27
Max Langhof Avatar answered Oct 06 '22 13:10

Max Langhof