如何将 Varaidic 模板化为 return 类型的模板?
How to template Varaidic as return type in template?
我遇到了可变模板问题。
上下文
一方面,我使用了一个功能类似于
的库
template<class ...Cols>
void doForVars(Cols&... args) {
...
}
另一方面,我有很多结构
struct Foo {
int id;
std::string name;
double length;
}
struct Bar {
int id;
double someProperty;
double someOther;
}
我必须为他们每个人使用专门的模板
template<>
void doSomething(Foo& foo) {
//boilerplate
doForVars( foo.id, foo.name, foo.length);
}
template<>
void doSomething(Bar& bar) {
//boilerplate
doForVars( bar.id, bar.someProperty, bar.someOther);
}
问题与期望
由于我得到了 50 多种类型,例如 Foo
和 Bar
,具有 10 到 30 个字段,因此有很多样板文件可供复制和粘贴。
它很容易出错,而且违反了 DRY,我可以忍受 :)
因此,我正在寻找为函数 listFields(T& t)
创建模板的方法,该函数 列出 记录 T 的字段,其 return 值为doForVars(Cols...&cols)
.
可以接受
template<typename T>
void doSomething(T& t) {
//boilerplate
doForVars(listFields(t));
}
但是我没有成功将这个模板returnint写成可变类型。
template<typename T, ?>
<I don't know> listFields(T& t);
比如我可以写
template<>
<I don't know> listFields(Foo& foo) {
return <I don't know>( foo.id, foo.name, foo.length);
}
template<>
<I don't know> listFields(Bar& bar) {
return <I don't know>( bar.id, bar.someProperty, bar.someOther);
}
可变参数显示为输入,但如何将它们用于 输出?
提前致谢。
编辑
感谢您的回答,我正在阅读。正如我所写的,我得到了很多记录来描述和使用([SCOS 2000 MIB 记录类型])1
我用于可变参数的库就是这个宝石 https://github.com/d99kris/rapidcsv
我用它来解析一些制表分隔值文件并将每一行映射到我的记录。很流畅
作为解决方法,您可以使用 std::tuple
s,如下所示
#include <iostream>
#include <string>
#include <tuple>
using namespace std::string_literals;
struct A{
int int_a;
double double_a;
std::tuple<int&, double&> tuplize(){
return {int_a, double_a};
}
};
struct B{
std::string str_b;
bool bool_b;
std::tuple<std::string&, bool&> tuplize(){
return {str_b, bool_b};
}
};
template <class ... Args>
void do_for_vars(Args& ... args){
((std::cout << args << ", "), ...);
std::cout << "\n";
}
template <class ... Args>
void helper(std::tuple<Args...> t){
std::apply(do_for_vars<Args...>, t);
}
int main(){
A a{1, 1.4};
B b{"str"s, true};
helper(a.tuplize());
helper(b.tuplize());
}
对 C++17 有效。
您想要的可能会变得非常困难,我认为除了使用宏的形式之外没有通用的解决方案。
第一部分其实很简单,你可以使用 std::tie
和简单的 return 元组。
auto listFields(Foo& foo) {
return std::tie( foo.id, foo.name, foo.length);
}
我通常反对同时使用模板函数和完全专业化。重载已经可以完成大部分工作。有使用它们的理由吗?
感谢 auto
return 类型和 class 模板参数推导,无需编写任何类型。我认为没有更好的解决方案,在 C++ 中,不可能通过 return 语句 return 多个对象。
现在,关于调用那些库函数。
如果 doForVars
是您唯一需要调用的,那么最干净的解决方案可能基于 this answer:
template<typename T>
void call_doForVars(T& obj){
std::apply([](auto &&... args) { doForVars(args...); }, listFields(obj));
}
int main(){
Foo foo{1,"Quimby",176.5};
call_doForVars(foo);
}
另一方面,如果你有更多的库函数,你会遇到一个障碍,据我所知不可能传递模板函数,你需要将它传递给一个内部函数,因为参数是少数几个地方之一可以扩展参数包。 lambda 有点绕过这个,因为它可以在表达式中创建一个函数。
到目前为止,在这种情况下,我最好的建议是将之前的解决方案包装到一个宏中。您只需要为每个库函数执行一次。 (你也可以为 listFields
做,它会更干净)。
编辑
毕竟我确实找到了通用案例的解决方案:
template<typename...Args>
using fnc_t = void(*)(Args&... args);
auto listFields(Foo& foo) {
return std::tie( foo.id, foo.name, foo.length);
}
template<typename...Args>
void call(fnc_t<Args...> f, const std::tuple<Args&...>& t){
std::apply(f, t);
}
int main(){
Foo foo{1,"Quimby",176.5};
call(doForVars, listFields(foo));
}
最终并没有那么难,不过目前它只适用于函数。
一个缺点是您必须在每次调用中调用 listFields
,如果您的元组也来自其他来源,这可能会很方便。但如果他们不这样做,我们可以更进一步得到:
template<typename T>
struct helper{
using fnc_t = void;
};
template<typename...Args>
struct helper<std::tuple<Args&...>>{
using fnc_t = void(*)(Args&... args);
};
template<typename T>
using fnc_t2 = typename helper<decltype(listFields(std::declval<T&>()))>::fnc_t;
template<typename T>
void call2(fnc_t2<T> f, T& obj){
std::apply(f, listFields(obj));
}
int main(){
Foo foo{1,"Quimby",176.5};
call2(doForVars, foo);
}
比较啰嗦,不过call2
还是可以隐藏的,call2(doForVars, foo);
真是个不错的结果。 :)
这里是live demo.
您不能 return 一个函数的多种类型,即使它是一个可变参数模板。但是,您可以 return 一个 tuple
包含作为特定 class 成员的所有类型。此外,似乎没有理由为每个 Foo
、Bar
等编写模板专业化。相反,您可以只为每种类型的重载集添加一个函数:
std::tuple<int, std::string, double> listFields(Foo& foo)
{
return {foo.id, foo.name, foo.length};
}
std::tuple<int, double, double> listFields(Bar& bar)
{
return {bar.id, bar.someProperty, bar.someOther};
}
现在,由于 doForVars
是可变参数模板,您需要解压缩 tuple
returned 形式 listFields
,您可以使用 std::apply
像这样:
template<typename T>
void doSomething(T& t) {
// ...
std::apply([](auto &&... args) { doForVars(args...); }, listFields(t));
}
这是一个demo
我遇到了可变模板问题。
上下文
一方面,我使用了一个功能类似于
的库template<class ...Cols>
void doForVars(Cols&... args) {
...
}
另一方面,我有很多结构
struct Foo {
int id;
std::string name;
double length;
}
struct Bar {
int id;
double someProperty;
double someOther;
}
我必须为他们每个人使用专门的模板
template<>
void doSomething(Foo& foo) {
//boilerplate
doForVars( foo.id, foo.name, foo.length);
}
template<>
void doSomething(Bar& bar) {
//boilerplate
doForVars( bar.id, bar.someProperty, bar.someOther);
}
问题与期望
由于我得到了 50 多种类型,例如 Foo
和 Bar
,具有 10 到 30 个字段,因此有很多样板文件可供复制和粘贴。
它很容易出错,而且违反了 DRY,我可以忍受 :)
因此,我正在寻找为函数 listFields(T& t)
创建模板的方法,该函数 列出 记录 T 的字段,其 return 值为doForVars(Cols...&cols)
.
template<typename T>
void doSomething(T& t) {
//boilerplate
doForVars(listFields(t));
}
但是我没有成功将这个模板returnint写成可变类型。
template<typename T, ?>
<I don't know> listFields(T& t);
比如我可以写
template<>
<I don't know> listFields(Foo& foo) {
return <I don't know>( foo.id, foo.name, foo.length);
}
template<>
<I don't know> listFields(Bar& bar) {
return <I don't know>( bar.id, bar.someProperty, bar.someOther);
}
可变参数显示为输入,但如何将它们用于 输出?
提前致谢。
编辑
感谢您的回答,我正在阅读。正如我所写的,我得到了很多记录来描述和使用([SCOS 2000 MIB 记录类型])1
我用于可变参数的库就是这个宝石 https://github.com/d99kris/rapidcsv
我用它来解析一些制表分隔值文件并将每一行映射到我的记录。很流畅
作为解决方法,您可以使用 std::tuple
s,如下所示
#include <iostream>
#include <string>
#include <tuple>
using namespace std::string_literals;
struct A{
int int_a;
double double_a;
std::tuple<int&, double&> tuplize(){
return {int_a, double_a};
}
};
struct B{
std::string str_b;
bool bool_b;
std::tuple<std::string&, bool&> tuplize(){
return {str_b, bool_b};
}
};
template <class ... Args>
void do_for_vars(Args& ... args){
((std::cout << args << ", "), ...);
std::cout << "\n";
}
template <class ... Args>
void helper(std::tuple<Args...> t){
std::apply(do_for_vars<Args...>, t);
}
int main(){
A a{1, 1.4};
B b{"str"s, true};
helper(a.tuplize());
helper(b.tuplize());
}
对 C++17 有效。
您想要的可能会变得非常困难,我认为除了使用宏的形式之外没有通用的解决方案。
第一部分其实很简单,你可以使用 std::tie
和简单的 return 元组。
auto listFields(Foo& foo) {
return std::tie( foo.id, foo.name, foo.length);
}
我通常反对同时使用模板函数和完全专业化。重载已经可以完成大部分工作。有使用它们的理由吗?
感谢 auto
return 类型和 class 模板参数推导,无需编写任何类型。我认为没有更好的解决方案,在 C++ 中,不可能通过 return 语句 return 多个对象。
现在,关于调用那些库函数。
如果 doForVars
是您唯一需要调用的,那么最干净的解决方案可能基于 this answer:
template<typename T>
void call_doForVars(T& obj){
std::apply([](auto &&... args) { doForVars(args...); }, listFields(obj));
}
int main(){
Foo foo{1,"Quimby",176.5};
call_doForVars(foo);
}
另一方面,如果你有更多的库函数,你会遇到一个障碍,据我所知不可能传递模板函数,你需要将它传递给一个内部函数,因为参数是少数几个地方之一可以扩展参数包。 lambda 有点绕过这个,因为它可以在表达式中创建一个函数。
到目前为止,在这种情况下,我最好的建议是将之前的解决方案包装到一个宏中。您只需要为每个库函数执行一次。 (你也可以为 listFields
做,它会更干净)。
编辑
毕竟我确实找到了通用案例的解决方案:
template<typename...Args>
using fnc_t = void(*)(Args&... args);
auto listFields(Foo& foo) {
return std::tie( foo.id, foo.name, foo.length);
}
template<typename...Args>
void call(fnc_t<Args...> f, const std::tuple<Args&...>& t){
std::apply(f, t);
}
int main(){
Foo foo{1,"Quimby",176.5};
call(doForVars, listFields(foo));
}
最终并没有那么难,不过目前它只适用于函数。
一个缺点是您必须在每次调用中调用 listFields
,如果您的元组也来自其他来源,这可能会很方便。但如果他们不这样做,我们可以更进一步得到:
template<typename T>
struct helper{
using fnc_t = void;
};
template<typename...Args>
struct helper<std::tuple<Args&...>>{
using fnc_t = void(*)(Args&... args);
};
template<typename T>
using fnc_t2 = typename helper<decltype(listFields(std::declval<T&>()))>::fnc_t;
template<typename T>
void call2(fnc_t2<T> f, T& obj){
std::apply(f, listFields(obj));
}
int main(){
Foo foo{1,"Quimby",176.5};
call2(doForVars, foo);
}
比较啰嗦,不过call2
还是可以隐藏的,call2(doForVars, foo);
真是个不错的结果。 :)
这里是live demo.
您不能 return 一个函数的多种类型,即使它是一个可变参数模板。但是,您可以 return 一个 tuple
包含作为特定 class 成员的所有类型。此外,似乎没有理由为每个 Foo
、Bar
等编写模板专业化。相反,您可以只为每种类型的重载集添加一个函数:
std::tuple<int, std::string, double> listFields(Foo& foo)
{
return {foo.id, foo.name, foo.length};
}
std::tuple<int, double, double> listFields(Bar& bar)
{
return {bar.id, bar.someProperty, bar.someOther};
}
现在,由于 doForVars
是可变参数模板,您需要解压缩 tuple
returned 形式 listFields
,您可以使用 std::apply
像这样:
template<typename T>
void doSomething(T& t) {
// ...
std::apply([](auto &&... args) { doForVars(args...); }, listFields(t));
}
这是一个demo