简体   繁体   English

如何实现可变参数模板,将用户输入读入提供的所有变量中?

[英]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 T值,字符串描述符

  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 ; 此外,您在对generic_reader的递归调用上缺少... it should be generic_reader(args...) . 它应该是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. 在倒数第二次调用时,其余参数为(z, "z") ,它成功替换为模板。 But after that, there is a final call to generic_reader() , with no remaining arguments. 但是在那之后,最后调用了generic_reader() ,没有剩余参数。 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. 最后一点-您会注意到我是first通过引用传递的,因此我可以写原始变量。 If you do this, make sure that the remaining Args... is passed by reference as well! 如果这样做,请确保其余的Args...也通过引用传递! Otherwise, recursive calls will pass the remaining args by value, and calls after the first one will no longer reference the original variables. 否则,递归调用将按值传递其余的args,并且第一个之后的调用将不再引用原始变量。

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. 在我看来,您使用的是std::pairs序列,其中第一种是固定类型std::string ,第二种是可变类型。

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...); ) 不幸的是,我不知道(在c ++ 17之前)函数主体中如何使用ps... ,因此,在C ++ 11和C ++ 17中,我能想到的最好的解决方案是基于递归(与您的原始内容相同,递归调用已在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 从C ++ 17开始,它提供了一种新的(且功能更强大)的可变参数使用模式(查找“折叠表达式”),并且您的函数可以简单地编写为

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 以下是完整的C ++ 17示例

#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): 如果您不想使用递归,则可以始终使用它(c ++ 14,但是对于c ++ 11存在index_sequence实现):

#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] [现场演示]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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