首页 > 解决方案 > 如何使用未知数量的参数指定指向 C 函数的 C++ 指针?

问题描述

我正在编写一个 C 库,我希望它可以在 C 和 C++ 中使用。在某一时刻,它应该从用户那里接受一个带有 0-3 个参数的回调,稍后将在某个指针处调用它。像这样(代码的副本也可以作为GitHub Gist获得):

// app_c.c
#include <stdio.h>
#include "lib.h"

double f0(void) {
    return 123;
}

double f2(double a, double b) {
    return a + b;
}

int main() {
    cb_arity = 0;
    cb_func = f0;
    printf("%f\n", cb_call());

    cb_arity = 2;
    cb_func = f2;
    printf("%f\n", cb_call());
}

我能够创建一个指向 C 函数的指针,该函数接受未知(但仍然固定)数量的参数,注意它是void (*cb_func)(),而不是void (*cb_func)(void)

// lib.h
#ifndef LIB_H_
#define LIB_H_

#ifdef __cplusplus
extern "C" {
#endif

extern int cb_arity;
extern double (*cb_func)();
double cb_call(void);

#ifdef __cplusplus
}
#endif

#endif  // LIB_H_
// lib.c
#include "lib.h"
#include <stdlib.h>

int cb_arity;
double (*cb_func)();

double cb_call(void) {
    switch (cb_arity) {
        case 0:
            return cb_func();
        case 1:
            return cb_func(10.0);
        case 2:
            return cb_func(10.0, 20.0);
        case 3:
            return cb_func(10.0, 20.0, 30.0);
        default:
            abort();
    }
}

它可以在我的机器和Wandbox上成功编译和运行。据我了解,没有调用 UB。

现在我想让它也可以在 C++ 中工作。不幸的是,看起来我现在需要reinterpret_cast,因为()在 C++ 中意味着“无参数”,而不是“未知数量的参数”:

// app_cpp.cpp
#include <stdio.h>
#include "lib.h"

int main() {
    cb_arity = 0;
    cb_func = []() { return 123.0; };
    printf("%f\n", cb_call());

    cb_arity = 2;
    cb_func = reinterpret_cast<double(*)()>(static_cast<double(*)(double, double)>(
        [](double a, double b) { return a + b; }
    ));
    printf("%f\n", cb_call());
}

据我了解,这里也没有调用 UB:虽然我将函数指针转换double(*)(double, double)double(*)(void)C++ 中的函数指针,但在调用之前它已转换回double(*)(double, double)C 代码中。

有什么办法可以摆脱 C++ 代码中这些丑陋的演员表吗?我试过指定cb_funcas的类型void(*)(...),但 C++ 仍然不会隐式转换double(*)(double, double)为它。

标签: c++clanguage-lawyerffireinterpret-cast

解决方案


您可以保留它,而不是从回调中删除参数的数量。

// lib.h
#ifndef LIB_H_
#define LIB_H_

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    int arity;
    union {
        void(*zero)(void);
        void(*one)(double);
        void(*two)(double, double);
        void(*three)(double, double, double);
    }
} cb_type;
extern cb_type cb;

double cb_call(void);

#ifdef __cplusplus
}
#endif

#endif  // LIB_H_
// lib.c
#include "lib.h"
#include <stdlib.h>

cb_type cb;

double cb_call(void) {
    switch (cb.arity) {
        case 0:
            return cb.zero();
        case 1:
            return cb.one(10.0);
        case 2:
            return cb.two(10.0, 20.0);
        case 3:
            return cb.three(10.0, 20.0, 30.0);
        default:
            abort();
    }
}

如果您不公开cb,则不能不匹配 arity 和 union 成员:

// lib.h
#ifndef LIB_H_
#define LIB_H_

#ifdef __cplusplus
extern "C" {
#endif

void register_zero(void(*)(void));
void register_one(void(*)(double));
void register_two(void(*)(double, double));
void register_three(void(*)(double, double, double));

double cb_call(void);

#ifdef __cplusplus
}
#endif

#endif  // LIB_H_

推荐阅读