Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to template Varaidic as return type in template?

I'm stuck with variadic template problem.

Contexte

On one hand, I use a library whose functions look like

template<class ...Cols>
void doForVars(Cols&... args) {
   ...
}

And on the other hand I have a lot of structs

struct Foo {
    int id;
    std::string name;
    double length;
}

struct Bar {
    int id;
    double someProperty;
    double someOther;
}

I have for each of them to use a specialized template


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

Problem and expectations

As I get more than 50 types like Foo and Bar with 10 to 30 fields, there a lot of boilerplate to copy and paste. It's error-prone and such a violation of DRY that I can stand for it :)

So, I'm searching for to create a template for a function listFields(T& t) that just list fields of record T and whose return value is acceptable for doForVars(Cols...&cols).

template<typename T>
void doSomething(T& t) {
    //boilerplate 
    doForVars(listFields(t)); 
}

But I don't succeed to write this template returnint a variadic type.

template<typename T, ?>
<I don't know> listFields(T& t);

such as I could just write

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

variadic are shown as input, but how to use them for output ?

Thanks by advance.

EDIT

Thank you for your answers I'm reading. As I wrote, I get a lot of record to describe and use ([SCOS 2000 MIB records types])1

The library I use for variadic is this jewel https://github.com/d99kris/rapidcsv

I use it to parse some tabulation separated value files and map each row to my records. Pretty smooth.

like image 542
MarcDeXeT Avatar asked Jan 25 '23 14:01

MarcDeXeT


1 Answers

Valid for C++17.

What you want might get quite hard and I do not think there is a generic solution apart form using macros.

The first part is actually easy, you can use std::tie and simply return tuples.

auto listFields(Foo& foo) {
    return std::tie( foo.id, foo.name, foo.length); 
}

I am generally against using template functions and full specialization together. Overloads can do most of the job already. Is there a reason for using them?

Thanks to auto return type and class template argument deduction, there is no need to write any types. I do not think there is a better solution, in C++ it is not possible to return multiple object through the return statement.

Now, about calling those library functions.

If doForVars is the only one you need to call, then the cleanest solution is probably based on 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);
}

On the other hand, if you have more library functions, you hit a snag that it is AFAIK impossible to pass template function around and you need to pass it into an inner function because the arguments are one of the few places a parameter pack can be expanded. The lambda kinda bypasses this because it can create a function inside an expression.

My best advice so far in this case is to just wrap the previous solution into a macro. You only need to do it once for each library function. (You can do it for listFields too, it will be cleaner).

EDIT

I did find a solution for the generic case after all:

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

Was not that hard in the end, it only works with functions for now though. One downside is that you have to call listFields in every call, which might be handy if your tuples come from other sources too. But if they don't, we can go one step further and get:

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

It's more mouthful, but call2 can be hidden anyway and call2(doForVars, foo); is really nice result. :)

Here is a live demo.

like image 86
Quimby Avatar answered Jan 30 '23 00:01

Quimby