I'm stuck with variadic template problem.
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);
}
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.
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.
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With