这种情况如何消除几百个 if/else

287 天前
 aqtata

运行时从外部读取一个 16 进制数字,然后调用对应的函数,比如读取到1F3,那么就调用函数foo_1f3,函数参数也是有编号的,规律是这样:

void foo_1f0(myclass_1f0& val);
void foo_1f1(myclass_1f1& val);
void foo_1f2(myclass_1f2& val);

之前 C#是用的反射,很容易实现。到 C++这不知道怎么搞比较优雅,目前有上百个 if/else 去判断然后调用。

C++这边可以用到 C++20 ,不知道有什么酷的解决方法?

9369 次点击
所在节点    C++
63 条回复
minami
286 天前
正解是 libffi
chandlerbing9317
286 天前
分支比较多改成表驱动就好了
ETiV
286 天前
都是 16 进制范围内就用偏移做呗
LuJyKa
286 天前
把这堆函数导出成 Dll ,然后用 GetProcAddress 直接传字符串得到函数地址,然后把参数地址传进去。
vituralfuture
286 天前
写个脚本,在构建阶段生成文件参与编译
liuidetmks
286 天前
@jcharr 要 runtime 吧
FrankFang128
286 天前
选中代码,让 AI 消除 if else
198plus
286 天前
建议代码生成,用“写代码的代码”维护代码,我记得 rust 编译器里面就有这种东西,用 python 脚本生成 rust 代码
bluearc
286 天前
用模板就可以解决,

```
#include <iostream>
#include <string>
#include <sstream>
#include <stdexcept>
#include <cstdint>


template<std::uint32_t N>
struct myclass {
void print() const {
throw std::logic_error("Error: myclass<" + std::to_string(N) + "> is not specialized.");
}
};

// 特化模板(如果需要为某些编号提供特定行为)
template<>
struct myclass<0x1f0> {
void print() const { std::cout << "Specialized myclass<1f0>\n"; }
};

template<>
struct myclass<0x2f0> {
void print() const { std::cout << "Specialized myclass<2f0>\n"; }
};

// 通用模板函数
template<std::uint32_t N>
void foo(myclass<N>& obj) {
std::cout << "Called foo<" << std::hex << N << ">\n";
obj.print(); // 调用特化模板的成员函数(如果有)
}

// 通用函数:根据运行时输入调用特定模板
void invoke_function(std::string_view hex_input) {
// 将字符串解析为 16 进制数值
std::uint32_t num;
std::stringstream ss;
ss << std::hex << hex_input;
ss >> num;
myclass<num> obj;
foo(obj);

}

// 主程序
int main() {
try {
invoke_function("1f0"); // 调用 foo<1f0>
invoke_function("2f0"); // 调用 foo<2f0>
invoke_function("3f0"); // 抛出异常
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << '\n';
}
return 0;
}

```

以后要添加新的,直接写个对应的特化模板就完事
abc612008
286 天前
@bluearc 这是 gpt 写的吗?模板参数丢个运行时变量进去怎么可能编译的过。
chendl111
286 天前
优雅没有任何意义
ysc3839
286 天前
@abc612008 用 stringstream 解析确实可能是 GPT 写的,但是直接传参(非运行时,代码里写的是字符串字面量)再转成常量是可行的,constexpr 就可以。
woniu7
286 天前
优雅没有任何意义
ysc3839
286 天前
@bluearc @abc612008
是我搞错了,用这种方式还是得把可能的取值写出来,不然模板不会实例化,还是避免不了 if else 。
这么写只能让代码更直观,因为数值不是写在函数名内。
mayli
286 天前
我觉得你最好还是给个最小化的例子说下参数怎么不同,不然没法准备参数…
ysc3839
285 天前
@bluearc @abc612008
不对,模板可以解决,需要手动把所有可能的取值写出来,例如:
test<0x1f0, 0x1f1, 0x1f2>(i);

如果能接受非标准扩展的话,有种办法可以把取值加到宏列表里:
#include <cstdio>
#include <cstdlib>

template<int N>
void func();

template<typename T = void>
void test(int n)
{
puts("test() failed!");
}

template<int N, int... ints>
void test(int n)
{
printf("test<%i>(%i)\n", N, n);
if (n == N) {
func<N>();
} else {
test<ints...>(n);
}
}

#define PUSHVAL _Pragma("push_macro(\"VALUES\")")
#define POPVAL _Pragma("pop_macro(\"VALUES\")")

#define VALUES 2
template<>
void func<2>()
{
puts("func<2>()");
}

PUSHVAL
#undef VALUES
#define VALUES POPVAL VALUES, 3
template<>
void func<3>()
{
puts("func<3>()");
}

PUSHVAL
#undef VALUES
#define VALUES POPVAL VALUES, 5
template<>
void func<5>()
{
puts("func<5>()");
}

PUSHVAL
#undef VALUES
#define VALUES POPVAL VALUES, 7
template<>
void func<7>()
{
puts("func<7>()");
}

int main(int argc, char** argv)
{
if (argc != 2) {
printf("usage: %s <int>\n", argv[0]);
return 1;
}

int n = (int)strtoul(argv[1], nullptr, 0);
printf("n = %i\n", n);

test<VALUES>(n);
}

上述方法在 gcc 中有效,msvc 无效。
bluearc
285 天前
@ysc3839 #56 我的,让 chatgpt 生成后感觉差不多就发了,上班摸鱼修改了下:
```
#include <cstdint>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>

class BaseClass {
public:
virtual ~BaseClass() = default;
virtual void print() = 0;
};

std::map<uint32_t, BaseClass *> reg;

template <std::uint32_t N> struct myclass : public BaseClass {
void print() override {
throw std::logic_error("Error: myclass<" + std::to_string(N) +
"> is not specialized.");
}
};

template <uint32_t N> void register_classes() {
reg[N] = new myclass<N>();
if constexpr (N > 0) {
register_classes<N - 1>();
}
}

template <> struct myclass<0x1ff> : public BaseClass {
void print() override { std::cout << "Specialized myclass<1f0>\n"; }
};
template <> struct myclass<0x1fa> : public BaseClass {
void print() override { std::cout << "Specialized myclass<2f0>\n"; }
};

// 通用模板函数
void foo(BaseClass *obj) {
obj->print(); // 调用特化模板的成员函数(如果有)
}

// 通用函数:根据运行时输入调用特定模板
void invoke_function(std::string_view hex_input) {
// 将字符串解析为 16 进制数值
std::uint32_t num;
std::stringstream ss;
ss << std::hex << hex_input;
ss >> num;
foo(reg[num]);
}

// 主程序
int main() {
register_classes<0xfff>();

try {
invoke_function("1ff");
invoke_function("1fa");
invoke_function("3f0");
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << '\n';
}
for (auto &pair : reg) {
delete pair.second;
}
return 0;
}
```

还是写特化模板就行,编译时给编译器传一个参数:-ftemplate-depth=4096 ;
Nimrod
285 天前
运行时值到类型的分发,具体的,这里就是`N` 到 `void foo<N>(myclass<N>&)`。
这里存在一个问题需要楼主表达清楚,对应类型的参数是如何构造出来的。
这里为作简化`void foo<N>()`

核心逻辑是做一次`int V`到`std::integral_constant<int, V>`的映射,再用 lambda 包装一下原本的`foo<N>`使得能用上推断出来的类型。

```cpp
#define DISPATCH(VALUE) \
case VALUE: \
return f(std::integral_constant<int, VALUE>{});


// trampline function
template <typename F>
auto dispatcher(F&& f, int value) {
switch(value) {
DISPATCH(0x1f0)
DISPATCH(0x1f1)
DISPATCH(0x1f2)
default:
throw std::runtime_error("Unregistered value");
}
}

void foo_wrapper(int num) {
dispatcher([](auto type){
constexpr auto v = decltype(type)::value;
foo<v>();
}, num);
}
```
这里,`dispatcher`是可以完全可以复用的。
[Demo]( https://godbolt.org/z/5TdevE4We)

剩下的就是手动`DISPATCH(N)`来注册你需要的值,也可以使用 BOOST_PP_REPEAT 来生成代码。
[Demo]( https://godbolt.org/z/1893bzEs8)

运行时的类型分发可以参考我的这篇博客,https://nimrod.blog/posts/cpp-elegant-ways-to-map-runtime-values-to-types
abc612008
285 天前
@ysc3839 #56 你这个调用一次 func 的时间是 O(n)的吗..难道要从 0 开始试
```
❯ ./a.out 7
n = 7
test<2>(7)
test<3>(7)
test<5>(7)
test<7>(7)
func<7>()
```
ysc3839
285 天前
@abc612008 手动写 if else 也是一个个试,你去掉 test 中的 printf ,看编译后的代码,和手写 if else 是一致的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1105424

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX