我使用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. 我想简化这个过程
如果你的所有参数都是可简单复制的(跨越 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)
一般来说,如果您想要自记录代码,则需要一个代码生成器,即消费者可以阅读代码以了解如何使用它。
在代码中执行此操作的唯一方法需要可变参数模板。我在有限的情况下使用了 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 中的代码,使用单独的定义。