如何将 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 多种类型,例如 FooBar,具有 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::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());

}

对 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 成员的所有类型。此外,似乎没有理由为每个 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 是可变参数模板,您需要解压缩 tuple returned 形式 listFields,您可以使用 std::apply像这样:

template<typename T>
void doSomething(T& t) {
    // ...
    std::apply([](auto &&... args) { doForVars(args...); }, listFields(t)); 
}

这是一个demo