How to use a preprocessor to separate parameters from function signatures in C++?

112 Views Asked by At

I am using a C++preprocessor to batch generate function definitions, but there is an issue when passing parameters

#include <iostream>

#define FOR_EACH_FUNC(_) \
    _(int, add, int a, int b) \
    _(int, subtract, int a, int b)

#define FORWARD_FUNC(ret, func, ...) \
    ret func(__VA_ARGS__) { \
        impl->func(__VA_ARGS__); \        // error!
    } \

FOR_EACH_FUNC(FORWARD_FUNC)

int main()
{
    int r1 = add(1, 2);
    int r2 = subtract(1, 2);
    return 0;
}

The result I want is

int add(int a, int b) {
    impl->add(a, b);
}

But the current result is

int add(int a, int b) {
    impl->add(int a, int b);
}

Is there any way to solve this problem? Thanks

I tried using C++'s variable parameter template to solve it, but it was not successful

Supplementary explanation:

We have a library A that needs to be used by a third party, but it relies on many other libraries. In order to clean up the compilation dependencies of others, we have implemented an intermediate library B that calls the methods of A through dlopen/dlsys (library A and its dependencies will be built into our system).

The problem here is: to implement a function, we need to first implement it in A, then define it in B as well, then define the function pointer, find the corresponding function address, and implement a simple logic to forward the request to A. I want to simplify this process

2

There are 2 best solutions below

2
Artyer On BEST ANSWER

If all your arguments are trivially copyable (which stuff crossing a DLL boundary should be), you can use this:

#define FOR_EACH_FUNC(_) \
    _(int, add, int(a), int(b)) \
    _(int, subtract, int(a), int(b))

Which will just be a bracketed name in the function declaration but be a functional cast as an expression.

This won't work with multi-word type names like unsigned long long, but you can use a typedef (using ull = unsigned long long;) or std::type_identity_t<unsigned long long>.


If you don't mind automatically named parameters (int _0, int _1), you can do this with Boost.Preprocessor:

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/comma_if.hpp>
#include <boost/preprocessor/repeat.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/variadic/size.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>

#define NUMBERED_ARGUMENTS_DECL_IMPL(R, PREFIX, I, TYPE) BOOST_PP_COMMA_IF(I) TYPE BOOST_PP_CAT(PREFIX, I)
#define NUMBERED_ARGUMENTS_DECL(...) BOOST_PP_SEQ_FOR_EACH_I(NUMBERED_ARGUMENTS_DECL_IMPL, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
// `NUMBERED_ARGUMENTS_DECL(int, char, void*)` -> `int _0, char _1, void* _2`

#define NUMBERED_ARGUMENTS_EXPR_IMPL(Z, I, PREFIX) BOOST_PP_COMMA_IF(I) BOOST_PP_CAT(PREFIX, I)
#define NUMBERED_ARGUMENTS_EXPR(...) BOOST_PP_REPEAT(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), NUMBERED_ARGUMENTS_EXPR_IMPL, _)
// `NUMBERED_ARGUMENTS_DECL(int, char, void*)` -> `_0, _1, _2`

#define FOR_EACH_FUNC(_) \
    _(int, add, int, int) \
    _(int, subtract, int, int)

#define FORWARD_FUNC(ret, func, ...) \
    ret func(NUMBERED_ARGUMENTS_DECL(__VA_ARGS__)) { \
        return impl->func(NUMBERED_ARGUMENTS_EXPR(__VA_ARGS__)); \
    } \

FOR_EACH_FUNC(FORWARD_FUNC)
0
Swift - Friday Pie On

Preprocessor lacks some functionality In general this would require a code generator, if you want a self-documenting code, i.e. consumer can read code in order to figure out how to use it.

The only way doing this in code requires a variadic template. I used for a prototyping an interface for Protobuf a limited case, where a function is a member of class template and it passes arguments as a whole pack:

template <class T>
struct UniversalBufTraits;

// There were some other declarations 


// Declares const and non-const version
// There might be overloaded versions of FuncName
#define DECLARE_METHOD(MethodName, ClassType, FuncName)     \
 template<class ClassType, typename ...Args>                \
        auto MethodName(ClassType& impl, Args... args) -> decltype(impl.FuncName(args...)) \
        { return impl.FuncName(args...); } \
 template<class ClassType, typename ...Args>                \
        auto MethodName(const ClassType& impl, Args... args) const -> decltype(impl.FuncName(args...)) \
        { return impl.FuncName(args...); }
  
template <>
struct UniversalBufTraits<ImplClass> {
    // bridge
    DECLARE_METHOD(tableSize, ImplClass, table_size);
    DECLARE_METHOD(getTable, ImplClass, table);
    DECLARE_METHOD(muTable, ImplClass, mutable_table);
    DECLARE_METHOD(addTableItem, ImplClass, add_table);
};

now these functions were used internally and not exposed to end user. The 'impl' is a private class of Bridge pattern which used this as an interface. It looks simpler if we call local member function:

// Declares const and non-const version of local  member function
#define DECLARE_MY_METHOD(MethodName, FuncName)     \
     template<typename ...Args>             \
            auto MethodName(Args... args) -> decltype(this->FuncName(args...)) \
            { return this->FuncName(args...); } \
     template<typename ...Args>             \
            auto MethodName(Args... args) const -> decltype(this->FuncName(args...)) \
            { return this->FuncName(args...); }


template <>
struct UniversalInterface<ImplClass> : protected ImplClass {
    // bridge
    DECLARE_MY_METHOD(tableSize,    table_size);
    DECLARE_MY_METHOD(getTable,     table);
    DECLARE_MY_METHOD(muTable,      mutable_table);
    DECLARE_MY_METHOD(addTableItem, add_table);
};

That suspiciosly looks like a CRTP.

For loading functions from shared library or DLL you already have function types (for pointers). You can use a function type as a declaration of function, which can be used in macrodefinitions:

using FooFuncType = void (int, float); 

FooFuncType   *foo;   // this is a pointer to a loaded function

FooFuncType   myFoo;  // this is a declaration of function with same signature as `foo`!

struct ClassA {
   FooFuncType  foo;  // method with same signature. 
};

That's all what might be exposed to consumer. Some widely used libraries and API exploit is, altohough it doesn't make themsafe (e.g. OpenGL). For code in .cpp a separate definition is used.