如何将Varaidic模板化为模板中的返回类型



我遇到了可变模板问题。

上下文

一方面,我使用了一个函数看起来像的库

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多个类型,如FooBar,其中有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,其中包含特定类的所有成员类型。此外,似乎没有理由为FooBar等中的每一个编写模板专用化。相反,您可以为每种类型的重载集添加一个函数:

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::tuples,如下

#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());
}

相关内容

  • 没有找到相关文章

最新更新