2

Suppose I want to implement something like std::tuple myself, just the basics. I want to show a failed attempt first.

#include <utility>
#include <iostream>

template <std::size_t I>
struct tuple_index_leaf {
    using Index = std::integral_constant<std::size_t, I>;
    std::size_t i = Index::value;
};

template <std::size_t... Is>
struct tuple_index : tuple_index_leaf<Is>...
{};

template <std::size_t I, std::size_t... Is>
constexpr auto get_index(tuple_index<Is...> const &i) {
    return static_cast<const tuple_index_leaf<I>*>(&i)->i;
}

template <std::size_t I, typename T>
struct tuple_leaf : tuple_index_leaf<I> {
    T elem;
};

template<typename... Ts>
struct tuple : tuple_leaf<sizeof...(Ts), Ts>... {

};

template <std::size_t I, typename... Ts>
auto& get(tuple<Ts...> &t) {
    return static_cast<tuple_leaf<I, float>*>(&t)->elem;
}

int main() {
    tuple_index<0, 1, 2> ti;
    std::cout << get_index<0>(ti) << "\n";
    tuple<int, float> t;
    get<2>(t) = 3.14;
} 

Now, take a look at get function. I hard coded the last type float and I can only call this with index 2, like get<2>. This is because the deficiency in my tuple constructor. If you look there, you will see that I am passing sizeof...(Ts) to tuple_leaf. For example, in this case, all my tuple leaves will be like tuple_leaf<2, int>, tuple_leaf<2, float>. What I wanted was an expansion like tuple_leaf<0, int>, tuple_leaf<1, float>.... The expansion I used, tuple_leaf<sizeof...(Ts), Ts>... does not give me these, I know. I need some sort of index sequence I figured and started implementing something like tuple_index. But that one requires me to pass std::size_t... and I don't know how to do that. So the question is, how can I get an expansion like tuple_leaf<0, int>, tuple_leaf<1, float>...?

1 Answer 1

2

It is not hard. Here is one example how to do this (not claiming the only one way, this was something I put together quickly):

#include <utility>
#include <cstddef>

template <std::size_t I, typename T>
struct tuple_leaf {
    T elem;
};

template<class SEQ, class... TYPE> struct tuple_impl;

template<size_t... Ix, class... TYPE>
struct tuple_impl<std::index_sequence<Ix...>, TYPE...> : tuple_leaf<Ix, TYPE>... { };

template<typename... Ts>
struct tuple : tuple_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...> { };


// below lines are for testing
tuple<int, double, char*> tup;

// the fact that this compiles tells us char* has index 2
auto& z = static_cast<tuple_leaf<2, char*>&>(tup);
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks. But testing it, I realized we lose all type safety. Do you know why the program on following link compiles, where I assign double to std::string? wandbox.org/permlink/uDBZAz5CqgeIhygW
@meguli this is a completely different question. std::string k; k = 3.14; compiles. If you are unsure why, you should ask a separate question. Rest assured, no type safety is lost here.
Wow, it totally compiles. My bad, sorry!
Just a quick clarification, you pass an index_sequence to tuple_impl by make_index_sequence but the partial specialization with size_t ...Ix is instantiated. I am having some trouble understanding exactly how that works. Could you explain?
@meguli not exactly sure what you are asking. The partial specialization will always be preferred over general template if it fits.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.