Why can we not access elements of a tuple by index? - c++

tuple <int, string, int> x=make_tuple(1, "anukul", 100);
cout << x[0]; //1
cout << get<0>(x); //2
2 works. 1 does not.
Why is it so?
From Lounge C++ I learnt that it is probably because the compiler does not know what data type is stored at that index.
But it did not make much sense to me as the compiler could just look up the declaration of that tuple and determine the data type or do whatever else is done while accessing other data structures' elements by index.

Because [] is an operator (named operator[]), thus a member function, and is called at run-time.
Whereas getting the tuple item is a template mechanism, it must be resolved at compile time. Which means this can be only done with the <> templating syntax.
To better understand, a tuple may store different types. A template function may return different types depending on the index passed, as this is resolved at compile time.
The operator[] must return a unique type, whatever the value of the passed parameter is. Thus the tuple functionality is not achievable.
get<0>(x) and get<1>(x) are two different functions generated at compile time, and return different types. The compiler generates in fact two functions which will be mangled to something like
int get_tuple_int_string_int_0(x)
and
string get_tuple_int_string_int_1(x)

The other answers here address the issue of why this isn't possible to implement, but it's also worth asking the question of whether it should be possible. (The answer is no.)
The subscript operator [] is semantically supposed to indicate dynamically-resolved access to a element of a collection, such as an array or a list (of any implementation). The access pattern generally implies certain things: the number of elements probably isn't known to the surrounding code, which element is being accessed will probably vary at runtime, and the elements are all of the same observable type (thus, to the calling code, interchangeable).
Thing is, a tuple isn't (that kind of) a collection. It's actually an anonymous struct, and its elements aren't interchangeable slots at all - semantically, they are regular fields. What's probably throwing you off is that they happen to be labelled with numbers, but that's really just an anonymous naming pattern - analogous to accessing the elements as x._0, x._1, etc. (The fact you can compute the field names at compile-time is a coincidental bonus enabled by C++'s type system, and is not fundamentally related to what a tuple is; tuples, and this answer, are not really specific to C++.)
So it doesn't support operator[] for the same reason that plain old structs don't support operator[]: there's no semantically-valid use for it in this context. Structures have a fixed set of fields that aren't interchangeable or dynamically computable, and since the tuple is a structure, not a collection, it follows the same rule. Its field names just look different.

It can be supported, it just needs to take a compile-time index. Since parameters of a function cannot be made constexpr, we need to wrap the index within a type and pass that instead. (e.g. std::integral_constant<std::size_t, N>.
The following is an extension of std::tuple that supports operator[].
template <typename... Ts>
class tuple : public std::tuple<Ts...> {
public:
using std::tuple<Ts...>::tuple;
template <std::size_t N>
decltype(auto) operator[](std::integral_constant<std::size_t, N>) {
return std::get<N>(*this);
}
};
It would be used like so:
tuple<int, std::string> x(42, "hello");
std::cout << x[std::integral_constant<std::size_t, 0>{}] << std::endl;
// prints: 42
To mitigate the std::integral_constant crazy, we can use variable template:
template <std::size_t N>
std::integral_constant<std::size_t, N> ic;
With this, we can say:
std::cout << x[ic<1>] << std::endl; // prints: hello
So it could be done. One guess as to why this is currently not available is because features such as std::integral_constant and variable templates may not have existed at the time std::tuple was introduced. As to why it doesn't exist even though those features exist, I would guess it's because no one have yet to proposed it.

It's not very clean supporting operator[] given you can't vary the static return type to match the accessed element. If the Standard Library had incorporated something like boost::any or boost::variant, it would make more sense.
Put another way, if you write something like:
int n = atoi(argv[1]);
int x = x[n];
Then what should it do if n doesn't address an int member of the tuple? To even support checking you'd need to store some manner of RunTime Type Information for tuples, which is extra overhead in the executable/memory.

Containers that support the subscript operator (i.e., operator[]) like std::vector or std::array are collections of homogenous values. Whatever the index provided to the subscript operator is, the value to return is always of the same type. Therefore, those containers can define a member function with the following declaration:
T& operator[](int);
Where T is the type of every element in the collection.
On the other hand, an std::tupe is a collection of heterogeneous values. The return value of a hypothetical subscript operator for std::tuple needs to vary with the index. Therefore, its return type depends on the index.
In the declaration of the operator[] given above, the index is provided as a function argument and therefore may be determined at run time. However, the return type of the function is something that needs to be determined at compile time, not at run time.
Since the return type of such a function depends on the index but must be determined at compile-time, the solution is to define instead a function template that accepts the index as a (non-type) template parameter. This way, the index is provided as a compile-time constant and the return type is able to change with the index:
template<std::size_t I, class... Types>
typename std::tuple_element<I, tuple<Types...>>::type& get(tuple<Types...>&) noexcept;
As you can see, std::get's return type depends on the index, I:
std::tuple_element<I, tuple<Types...>>::type&

Because tuple has no operator "bracket".
Why is it so? You cannot resolve templates based only on the return value. You cannot write
template<typename T>
T tuple::operator [](size_t i) const ;
Which is absolutely necessary to be able to allow statements like x[0]

Related

How to concisely express the C++20 concept of range containing T-typed values?

I want to write a function template that accepts ranges of MyType-typed values, so I wrote
void f(const std::ranges::range auto& my_range) { /* ... */ }
Unfortunately this does not constrain the type of the values contained in the range. I could use a requires clause before the function body block but I want something reusable and not specific to this function.
Naturally I wrote
template <class T, class V>
concept range_of = std::ranges::range<T> && std::is_same_v<V, std::ranges::range_value_t<T>>;
void f(const range_of<MyType> auto& my_range) { /* ... */ }
but I'm very surprised that this is not supported out-of-the-box by the standard library given how natural it seems. This also applies to std::ranges::view for which I could write a similar view_of concept.
Is my definition of range_of incomplete or wrong? Or is there a good reason this isn't offered by the standard library?
The problem here is: what is the actual constraint that you want?
Do you want to:
constrain on the range's value type being specifically T?
constrain on the range's value type being convertible to T?
constrain on the range's value type satisfying some other concept, C?
any of the above but instead about the range's reference type?
Different contexts call for different versions of this. Your range_of concept is a perfectly fine implementation of the first one of these (could use std::same_as instead of std::is_same_v but doesn't super matter). But what happens when you want something else? The specific choice of constraint is going to depend very much on what it is you want to do.
So the direct answer is: there's no range_of concept because there's really no one true answer of what that concept should do.
A different answer would be that this isn't even what would be useful. What would be useful would be a direct way to pull out the associated types of a given range to make it easy to add further constraints on them.
For instance, if we take Rust, we can define a function that takes an arbitrary iterator:
fn foo<I: Iterator>(it: I) { ... }
That's sort of akin to just void f(range auto). It's a constraint, but not a very useful one. But Iterator, in Rust, has an associated type: its Item. So if you wanted an Iterator over a specific type, that's:
fn foo<I: Iterator<Item=i32>>(it: I) { ... }
If you wanted an Iterator whose type satisfies some other constraint:
fn foo<T: Debug, I: Iterator<Item=T>>(it: I) { ... }
And it's really this ability - to pull out associated types and constrain on them directly - that we really lack.

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.
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.
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.
It looks very strange
This is a weak argument. Looks are a subjective matter.
The function parameter list is simply not an option for a value that is needed at compile time.
It makes indexing by runtime generated indices difficult
Runtime generated indices are difficult regardless, because C++ is a statically typed language with no runtime reflection (or even compile time reflection for that matter). Consider following program:
std::tuple<std::vector<C>, int> tuple;
int index = get_at_runtime();
WHATTYPEISTHIS var = get(tuple, index);
What should be the return type of get(tuple, index)? What type of variable should you initialise? It cannot return a vector, since index might be 1, and it cannot return an integer, since index might be 0. The types of all variables are known at compile time in C++.
Sure, C++17 introduced std::variant, which is a potential option in this case. Tuple was introduced back in C++11, and this was not an option.
If you need runtime indexing of a tuple, you can write your own get function template that takes a tuple and a runtime index and returns a std::variant. But using a variant is not as simple as using the type directly. That is the cost of introducing runtime type into a statically typed language.
Note that in C++17 you can use structured binding to make this much more obvious:
#include <iostream>
#include <tuple>
using namespace std;
int main()
{
auto t = make_tuple(1.0, "Two", 3);
const auto& [one, two, three] = t;
cout << "(" << one << ", "
<< two << ", "
<< three << ")\n";
}

Is there a reason for zero sized std::array in C++11?

Consider the following piece of code, which is perfectly acceptable by a C++11 compiler:
#include <array>
#include <iostream>
auto main() -> int {
std::array<double, 0> A;
for(auto i : A) std::cout << i << std::endl;
return 0;
}
According to the standard § 23.3.2.8 [Zero sized arrays]:
1 Array shall provide support for the special case N == 0.
2 In the case that N == 0, begin() == end() == unique value. The return value of
data() is unspecified.
3 The effect of calling front() or back() for a zero-sized array is undefined.
4 Member function swap() shall have a noexcept-specification which is equivalent to
noexcept(true).
As displayed above, zero sized std::arrays are perfectly allowable in C++11, in contrast with zero sized arrays (e.g., int A[0];) where they are explicitly forbidden, yet they are allowed by some compilers (e.g., GCC) in the cost of undefined behaviour.
Considering this "contradiction", I have the following questions:
Why the C++ committee decided to allow zero sized std::arrays?
Are there any valuable uses?
If you have a generic function it is bad if that function randomly breaks for special parameters. For example, lets say you could have a template function that takes N random elements form a vector:
template<typename T, size_t N>
std::array<T, N> choose(const std::vector<T> &v) {
...
}
Nothing is gained if this causes undefined behavior or compiler errors if N for some reason turns out to be zero.
For raw arrays a reason behind the restriction is that you don't want types with sizeof T == 0, this leads to strange effects in combination with pointer arithmetic. An array with zero elements would have size zero, if you don't add any special rules for it.
But std::array<> is a class, and classes always have size > 0. So you don't run into those problems with std::array<>, and a consistent interface without an arbitrary restriction of the template parameter is preferable.
One use that I can think of is the return of zero length arrays is possible and has functionality to be checked specifically.
For example see the documentation on the std::array function empty(). It has the following return value:
true if the array size is 0, false otherwise.
http://www.cplusplus.com/reference/array/array/empty/
I think the ability to return and check for 0 length arrays is in line with the standard for other implementations of stl types, for eg. Vectors and maps and is therefore useful.
As with other container classes, it is useful to be able to have an object that represents an array of things, and to have it possible for that array to be or become empty. If that were not possible, then one would need to create another object, or a managing class, to represent that state in a legal way. Having that ability already contained in all container classes, is very helpful. In using it, one then just needs to be in the habit of relating to the array as a container that might be empty, and checking the size or index before referring to a member of it in cases where it might not point to anything.
There are actually quite a few cases where you want to be able to do this. It's present in a lot of other languages too. For example Java actually has Collections.emptyList() which returns a list which is not only size zero but cannot be expanded or resized or modified.
An example usage might be if you had a class representing a bus and a list of passengers within that class. The list might be lazy initialized, only created when passengers board. If someone calls getPassengers() though then an empty list can be returned rather than creating a new list each time just to report empty.
Returning null would also work for the internal efficiency of the class - but would then make life a lot more complicated for everyone using the class since whenever you call getPassengers() you would need to null check the result. Instead if you get an empty list back then so long as your code doesn't make assumptions that the list is not empty you don't need any special code to handle it being null.

Generic container & variable element access

In my variable data, when running "add a variable" script code, how to define a generic container for all types? And what is the generic formula to access them? It's annoying because I have to define a vector template for each type (int float double etc). My variable should contain only and only a generic vector object, whatever it's int, or float or double etc. Is it possible? Any idea?
That's the whole point of the Standard Template Library..
std::vector<int>
std::vector<float>
Two vectors - the same class, templated with different types.
If you want a container of differing type you might want to look at std::tuple
If you want a single vector that contains objects of many different types, then you might want to use boost::any, or possibly boost::variant.
Edit:
oh, you want a container that can fit in any type? Yes you can. But within certain restrictions.
See boost::any and boost::variant. boost::variant would enable you to save data of several types enumerated when declaring the boost::variant. boost::any doesn't make you enumerate all types you want to support, but you need to cast them to get back the value yourself.
In short, you must either store the type information somewhere else ( when using boost::any ), or just support a few types (say, a heterogeneous vector that supports int and double using boost::variant)
template in C++ works exactly by eliminating the need to write the same class for every type.
For example:
// function template
template <class T>
T GetMax (T a, T b) {
T result;
result = (a>b)? a : b;
return (result);
}
this GetMax should work for whatever type that has a > operator. So that is exactly what template is for in C++.
If you need more help on implementing a vector on C++ (which, by the way, is not so simple when it comes to custom types that has its own constructor and destructor. You may need allocator to get un-initialized space), read this (Implementation of Vector in C++).

Tuple vs TypeTuple in D

What is the difference between Tuple and TypeTuple? I've looked at sample code in the library, but they look similar. How do I decide which to use? and is there a good reason why Tuple is in std.typecons but TypeTuple is in std.typetuple?
A Tuple is a data type, consisting of a collection of fields that you specify.
A TypeTuple is simply "a bunch of things" that the compiler knows about at compile time; it has no existence at run time.
(Contrary to its name, a TypeTuple can hold pretty much anything -- not just types!)
tuple std.typecons normal ordinary tuple of values.
typetuple in std.typetuple is tuple of types. It can be used at compile time like in this example function, where it limits allowed types for funciton to int, long and double only.
void foo (T) (T arg) {
static assert staticIndexOf!(T,TypeTuple!(int, long, double)) != -1,
T.stringof ~" is not allowed as argument for foo");
}
Tuple is a collection of variables (like a struct) while TypeTuple is a collection of type which you can use in template checks
Tuple!(int,"index",real,"value") var;
defines a variable with var.index a int and var.value a real
a TypeTuple is used when you want to examine whether the instantiations of your templates use the correct types