我遇到了可变模板问题。
上下文
一方面,我使用了一个函数看起来像的库
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的字段,并且其返回值对于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
我用于varadic的库就是这颗宝石https://github.com/d99kris/rapidcsv
我用它来解析一些以表格分隔的值文件,并将每一行映射到我的记录。相当光滑。
对C++17有效。
你想要的可能会变得很难,我认为除了使用宏之外,没有通用的解决方案。
第一部分实际上很简单,您可以使用std::tie
并简单地返回元组。
auto listFields(Foo& foo) {
return std::tie( foo.id, foo.name, foo.length);
}
我通常反对将模板函数和完全专业化结合使用。过载已经可以完成大部分工作了。使用它们有原因吗?
由于auto
返回类型和类模板参数推导,因此无需编写任何类型。我认为没有更好的解决方案,在C++中,不可能通过return语句返回多个对象。
现在,关于调用那些库函数。
如果doForVars
是您唯一需要调用的,那么最干净的解决方案可能基于以下答案:
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);
}
另一方面,如果你有更多的库函数,你会遇到一个障碍,即AFAIK不可能传递模板函数,你需要将其传递到内部函数中,因为参数是参数包为数不多的可以扩展的地方之一。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);
是一个非常好的结果。:(
这是一个现场演示。
即使函数是可变模板,也不能从函数返回多个类型。但是,您可以返回一个tuple
,其中包含特定类的所有成员类型。此外,似乎没有理由为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
是一个可变模板,您需要解压缩从listFields
返回的tuple
,为此可以使用std::apply
,如下所示:
template<typename T>
void doSomething(T& t) {
// ...
std::apply([](auto &&... args) { doForVars(args...); }, listFields(t));
}
这是的演示
作为一种变通方法,您可以使用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());
}