如何使用预处理器将 C++ 中的参数与函数签名分开?

问题描述 投票:0回答:2

我使用C++预处理器批量生成函数定义,但传递参数时出现问题

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

我想要的结果是

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

但是目前的结果是

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

有什么办法可以解决这个问题吗?谢谢

我尝试使用C++的可变参数模板来解决,但是没有成功

补充说明:

我们有一个库A需要第三方使用,但是它依赖于很多其他库。为了清理别人的编译依赖,我们实现了一个中间库B,通过

dlopen/dlsys
调用A的方法(库A及其依赖将内置到我们的系统中)。

这里的问题是:要实现一个函数,我们需要先在A中实现它,然后在B中也定义它,然后定义函数指针,找到对应的函数地址,并实现一个简单的逻辑,将请求转发到A. 我想简化这个过程

c++ c-preprocessor auto-generate
2个回答
1
投票

如果你的所有参数都是可简单复制的(跨越 DLL 边界的东西应该是),你可以使用这个:

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

这只是函数声明中的括号名称,但作为表达式的函数转换。

这不适用于像

unsigned long long
这样的多单词类型名称,但您可以使用 typedef (
using ull = unsigned long long;
) 或
std::type_identity_t<unsigned long long>


如果您不介意自动命名参数(

int _0, int _1
),您可以使用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
投票

一般来说,如果您想要自记录代码,则需要一个代码生成器,即消费者可以阅读代码以了解如何使用它。

在代码中执行此操作的唯一方法需要可变参数模板。我在有限的情况下使用了 Protobuf 接口的原型,其中函数是类模板的成员,并且它作为一个整体传递参数:

template <class T>
struct UniversalBufTraits;

// There were some other declarations 


// Declares const and non-const version
#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);
};

现在这些功能在内部使用,不暴露给最终用户。 “impl”是 Bridge 模式的私有类,它使用它作为接口。如果我们调用本地成员函数,看起来会更简单:

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

这看起来很像 CRTP。

要从共享库或 DLL 加载函数,您已经拥有函数类型(用于指针)。您可以使用函数类型作为函数的声明,可以在宏定义中使用:

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

这就是消费者可能接触到的所有内容。一些广泛使用的库和 API 漏洞是安全的(例如 OpenGL),但它并不保证它们的安全。对于 .cpp 中的代码,使用单独的定义。

© www.soinside.com 2019 - 2024. All rights reserved.