Search code examples
c++c++11templatesparameterspack

C++11: how to write a template function that works like get<tuple>... but receives param pack?


I know that in c++11, the get<> template function works with std::tuple to get the indexed value of the tuple, this is resolved at compile time.

But my requirement is to have a template function called get<> but receives a parameter pack, so the code should be like below:

#include<iostream>
using namespace std;
template<typename Head, typename ... Tail>
auto get(size_t index, Head&& t, Tail&&...tail){
    return get(index-1, tail...);
}
template<typename Head, typename ... Tail>
Head get<0>(Head&& t, Tail&&...tail){
    return t;
}
int main(){
    cout<<get(3,"abc",'x',27,"hello")<<endl;
    cout<<get(2,"abc",'x',28,"hello")<<endl;
    return 0;
}

Well it doesn't compile, for sure, as I don't know how to write such a "get" template function. I wish the main function will run and print as below:

hello
28

So my question: how to implement this "get" template as I mentioned above? Thanks!


Solution

  • Do you really need to be able to select the index at runtime? If not, following should be enough:

    template <std::size_t Index, typename ...P> auto get(P &&... params)
    {
        return std::get<Index>(std::make_tuple(params...));
    }
    
    // ...
    
    std::cout << get<1>("abc", 42, 123.456); // Prints 42
    

    If you indeed want to have the index as a normal (runtime) parameter, you'd have to return a std::variant<P...> (or std::any, or a tagged union), since a return type can't depend on a runtime parameter.

    A possible implementation could look like this:

    template <typename ...P> std::variant<P...> get(std::size_t index, P &&... params)
    {
        std::size_t pos = 0;
        std::variant<P...> ret;
        ((index == pos++ ? void(ret = params) : void()) , ...);
        return ret;
    }
    
    // ...
    
    std::cout << std::get<int>( get(1, "abc", 42, 123.456) ); // Prints 42 too, but looks ugly
    

    Or you could specify return type as a template parameter:

    template <typename T, typename ...P> T get(std::size_t index, P &&... params)
    {
        std::size_t pos = 0;
        std::variant<P...> va;
        ((index == pos++ ? void(va = params) : void()) , ...);
        if (T *ptr = std::get_if<T>(va))
            return T;
        else
            throw /*something*/;
    }
    
    // ...
    
    std::cout << get<int>(1, "abc", 42, 123.456); // 42
    

    Or you could pass the value to a function/functor instead: (thanks @Yakk)

    template <typename T, typename ...P> void get(std::size_t index, T &&func, P &&... params)
    {
        std::size_t pos = 0;
        ((index == pos++ ? void(func(params)) : void()) , ...);
    }
    
    // ...
    
    get(1, [](int x){std::cout << x;}, "abc", 42, 123.456); // 42
    // Alternative:
    // get(1, [](auto x){std::cout << x;}, "abc", 42, 123.456); // 42 too, 
    // but you could print any type which can be printed without having to 
    // specify the type manually.