1

Let's say I have a struct as a data context defined as this:

struct Ctx {
   TypeA a;
   TypeB b;
   TypeC c;
   TypeD d;
   TypeE e;
};

auto TestFunc(TypeA a, TypeB b, TypeC c, args...) -> result;

and calling would take the form of:

TestFunc(ctx.a, ctx.b, ctx.c, args...);

Since ctx is redundant, I'd like a new wrapper:

auto TestFunc(Ctx& ctx, args...) -> result {
    return TestFunc(ctx.a, ctx.b, ctx.c, args...);
}

Is there any way to map the type signature of the function to the type signature of the struct so I can create some sort of macro/generics combination that will work without having to manually write a wrapper for every function that accesses members in this Ctx?

JimmyStack
  • 25
  • 6
  • Can you not change `TestFunc` ? I think you are trying to solve the problem at the wrong end. If a function has to many parameters then you need to fix that function – 463035818_is_not_an_ai Feb 15 '23 at 10:04
  • are the functions variadic? Or is `...` just a placeholder in your example to illustrate that there are more parameter? – 463035818_is_not_an_ai Feb 15 '23 at 10:08
  • Can the struct Ctx be replaced/converted to a touple? `std::apply` might be a solution if that's the case. – Ignacio Gaviglio Feb 15 '23 at 11:12
  • First question: No, I cannot change TestFunc other than by wrapping it, it's a C library. I just want to avoid manual wrapping because there are hundreds of functions that take this form. Second question: Yes, it is also variadic so I'm forwarding all of the trailing parameters. – JimmyStack Feb 15 '23 at 11:12
  • Ignacio, I haven't looked too much into tuples, that could be an option. I'll check it out, thanks! – JimmyStack Feb 15 '23 at 11:25

3 Answers3

2

As I understand, you need 2 things:

  • one (or two with const) function to transform your struct as tuple:
struct Ctx {
   auto as_tuple() const { return std::tie(a, b, c, d, e); }
   auto as_tuple() { return std::tie(a, b, c, d, e); }

   TypeA a;
   TypeB b;
   TypeC c;
   TypeD d;
   TypeE e;
};

then, assuming types are distinct, a function (or 2 to support C-ellipsis) to filter from the tuple only the argument you want:

template <typename Tuple, typename Ret, typename... Ts, typename... Args>
Ret call(Ret(*func)(Ts...), Tuple&& tuple, Args&&... args)
{
    return func(std::get<Ts&>(tuple)..., std::forward<Args>(args)...);
}

template <typename Tuple, typename Ret, typename... Ts, typename... Args>
Ret call(Ret(*func)(Ts..., ...), Tuple&& tuple, Args&&... args)
{
    return func(std::get<Ts&>(tuple)..., std::forward<Args>(args)...);
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

I am not fully sure, what you want to achieve.

But maybe a variadic struct would help. You can derive the members of the struct from the initialization values. Of course also other deductions would be possible.

We can build the variadic struct on a std::tuple. And we can overload the function call operator and use std::apply to call the function with elements of the variadic struct.

See for example the below solution:

#include <iostream>
#include <functional>
#include <tuple>

// Variadic struct
template <typename...Args>
struct Ctx {
    // Variadic data
    std::tuple<Args...> data{};  
    // Variadic Constructor. Will use CTAD
    Ctx(Args...args) : data(std::forward<decltype(args)>(args)...) {};
    // Calling the function with the data of the struct
    void operator()(std::function<void(Args...)> f) {
        std::apply(f,data);
    }
};

// Your library function
void print(int i, char c, double d) {
    std::cout << i << ' ' << c << ' ' << d << '\n';
}
// Test code
int main() {

    Ctx v(1, 'a', 1.1);
    
    v (print);
}

Of course also other implementations would be possible. Please comment, if you need something else.

A M
  • 14,694
  • 5
  • 19
  • 44
  • What I want to achieve is I have a Context that contains cached handles, all of different types. There are hundreds of functions that can operate on the different members. I want to wrap them all so I just pass a context in and it forwards the appropriate members (by type) to the function to make the library easier to use. That way I don't have to write hundreds of function wrappers picking out the appropriate members to pass by hand. The suggestions I've seen so far seem to call for passing ALL members in, I only want to pass in the ones matching the function's type signature. – JimmyStack Feb 15 '23 at 14:21
  • In other words, if a function accepts members of type X and type Z but not type Y, I want to forward X and Z. I'm not sure this is possible, that's why I asked. Should clarify: I know I will have to do some wrapper work but if I could distill it down into a macro/with template so it's only a one liner for each function then I'd be happy with that. – JimmyStack Feb 15 '23 at 14:28
1

Intro

This is an old question but things have improved (slightly) in C++ since it was first asked and it may benefit from a fresh look.

Background

I'm thinking of the zpp::bits library and the "Killing C++ Serialization Overhead & Complexity - Eyal Zedaka - CppCon 2022" talk that its author gave at CppCon 2022.

What this library does is that it uses the structured bindings (c++17) in order to unpack classes members automagically, and it uses template magic to detect exactly how many members the class has. This process is not pretty as structured bindings don't support the variadic syntax yet, so it relies on the old trick of manually trying for case of 1 member, then 2, then 3, etc. This limits this library to 50 member classes for the moment. See this line for what I mean.

There are other limitations, like when dealing with private members, but for those I will refer you to the library's docs as in this specific case we have a simple struct with all public members.

So I'm thinking that for this problem you could use the same mechanism zpp::bits uses to unpack the class and then inject it into a variadic template function's argument list...

From what I've seen so far you could use the visit_members() function to do exactly what you need like this code in the library does.

This same solution could apply to this other SO question. Equally, the AggregatesToTuples library used in that question could be used here, but I prefer the zpp::bits approach.

Working example with zpp::bits

#include <iostream>
#include <https://raw.githubusercontent.com/eyalz800/zpp_bits/main/zpp_bits.h>

// Example types that can be passed in
struct Ctx1 {
    int i = 1;
};

struct Ctx2 {
    std::string s = "string";
    double d = 6.28;
};

// The test function OP requested to be called
template <typename... CtxItems>
auto TestFunc(CtxItems&&... ctx_items) -> void {
    std::cout << "TestFunc called with " << sizeof...(ctx_items) << " args:" << std::endl;
    ((std::cout << " - arg: " << ctx_items << std::endl), ...); // print the args
}

int main() {
    Ctx1 ctx1;
    zpp::bits::access::visit_members(ctx1, [](auto &&... items) constexpr {
            return TestFunc(items...);
        });
    
    Ctx2 ctx2;
    zpp::bits::access::visit_members(ctx2, [](auto &&... items) constexpr {
            return TestFunc(items...);
        });

    return 0;
}

Godbolt Demo

Output:

Program returned: 0
TestFunc called with 1 args:
 - arg: 1
TestFunc called with 2 args:
 - arg: string
 - arg: 6.28

Future

Should the "Structured Bindings can introduce a Pack (P1061R2)" paper land, this code would become a lot simpler and we could probably implement something directly as opposed to relying on the internals of zpp::bits.

nonsensickle
  • 4,438
  • 2
  • 34
  • 61