简体   繁体   中英

How do I implement a variadic template that reads user input into all variables supplied?

I am currently trying to teach myself variadic templates. However I am having trouble understanding anything past a simple adding template.

Currently I would want a template that would do the following:

  1. Take any number of types
  2. Takes parameters that requires the user to enter them in the following format:

    T value, string descriptor

  3. It then goes through each variable one by one, printing the descriptor before reading the variable

For example the output should look like this:

x (int) //this is the descriptor
//here something is being read into the variable x
y (int) //this is another descriptor
//something else is being read into y
.
.
.

Since its always the same operation, this should be possible. However my best try looked like this

template<typename t,typename... Args>
void generic_reader(t first,string desc,Args... args)
{
    cout<<desc<<endl;
    cin>>first;
    generic_reader(args);
}

Obviously this doesnt work. However I cant think of another way of doing this. Again I have only started to work with variadic templates.

Can someone show me a solution with a detailed explanation?

Here's one way, using recursion.

#include <iostream>

// provide a terminating case 
void generic_read()
{
}

// provide the general case which picks off the first 2 arguments
// and forwards the rest to another version of itself.

template<typename T, typename Printable, typename...Rest>
void generic_read(T& value ,Printable&& desc,Rest&&...rest)
{
    std::cout << desc << std::endl;
    std::cin >> value;
    generic_read(std::forward<Rest>(rest)...);
}

// test
int main()
{
    int x;
    double y;

    generic_read(x, "an integer:", y, "a double");
}

You're basically there -- you're just missing a base case. Also, you're missing the ... on your recursive call to generic_reader ; it should be generic_reader(args...) .

Here's some working code that does what you're trying to do:

#include <string>
#include <iostream>

void generic_reader()
{
    std::cout << "no more stuff!" << std::endl;
}

template <typename T, typename... Args>
void generic_reader(T& first, const std::string& desc, Args&... args)
{
    std::cout << desc << std::endl;
    std::cin >> first;
    std::cin.ignore(100, '\n');
    generic_reader(args...);
}


int main()
{
    int x, y, z;

    generic_reader(x, "x", y, "y", z, "z");

    std::cout << "x: " << x << " y: " << y << " z: " << z << std::endl;

    return 0;
}
`

Walking through the code: your approach was correct, but there's no base case when you run out of arguments. On the second to last call, the remaining arguments are (z, "z") , which substitutes into the template successfully. But after that, there is a final call to generic_reader() , with no remaining arguments. You need to provide a candidate that can accept the final (empty) argument list.

One final note -- you'll notice that I passed in first by reference, so I could write to the original variables. If you do this, make sure that the remaining Args... is passed by reference as well! Otherwise, recursive calls will pass the remaining args by value, and calls after the first one will no longer reference the original variables.

It seems to me that you're using a sequence of std::pairs where the first type is fixed, std::string , and the second one is a variable type.

So you can write your function as

template <typename ... Args>
void generic_reader (std::pair<std::string, Args> & ... ps)
 { /* do something */}

and call it as

auto a = std::make_pair<std::string>("a", short(0));
auto b = std::make_pair<std::string>("b", 1);
auto c = std::make_pair<std::string>("c", 2L);
auto d = std::make_pair<std::string>("d", 3LL);

generic_reader(a, b, c, d);

Unfortunately I don't know (before c++17) how to use ps... in the body of the function so, in C++11 and in C++17, the best I can think is a solution based on recursion (as your original, with the recursion call corrected in generic_reader(args...); )

Starting from C++17 it's available a new (and more powerful) mode of use variadic arguments (look for "fold expression") and your function ca be simply written as

template <typename ... Args>
void generic_reader (std::pair<std::string, Args> & ... ps)
 { ( (std::cout << ps.first << std::endl, std::cin >> ps.second), ... ) ; }

The following is a full working C++17 example

#include <utility>
#include <iostream>

template <typename ... Args>
void generic_reader (std::pair<std::string, Args> & ... ps)
 { ( (std::cout << ps.first << std::endl, std::cin >> ps.second), ... ) ; }

template <typename ... Args>
void variadic_printer (Args & ... as)
 { ( (std::cout << as.first << ", " << as.second << std::endl), ... ) ; }

int main ()
 { 
   auto a = std::make_pair<std::string>("a", short(0));
   auto b = std::make_pair<std::string>("b", 1);
   auto c = std::make_pair<std::string>("c", 2L);
   auto d = std::make_pair<std::string>("d", 3LL);

   generic_reader(a, b, c, d);

   variadic_printer(a, b, c, d);
 }

If you prefer not to use recursion you can always use this (c++14, but there exist implementations of index_sequence for c++11):

#include <utility>
#include <iostream>
#include <tuple>

template <class Tuple, std::size_t... Is>
void generic_reader_impl(std::index_sequence<Is...>, Tuple&& tuple) {
    std::size_t dummy[] = { 0ul, 
        (static_cast<void>(std::cout << std::get<2ul*Is + 1ul>(tuple) << std::endl),
         static_cast<void>(std::cin >> std::get<2ul*Is>(tuple)),
         Is)... 
    };
    static_cast<void>(dummy);
}

template <class... Args>
void generic_reader(Args&&... args) {
    generic_reader_impl(std::make_index_sequence<sizeof...(Args) / 2>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}

int main() {
    int x;
    double y;
    generic_reader(x, "an integer:", y, "a double");
    std::cout << x << std::endl;
    std::cout << y << std::endl;
}

Output:

1
1.2

[live demo]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM