You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在std::any中存储成员函数指针的签名异常问题及解决方法

成员函数指针存入std::any的签名不匹配问题分析与解决

问题背景

在实现成员函数映射时,发现将成员函数指针存入std::any后,调用阶段的类型签名与存储阶段不一致,导致无法通过std::any_cast调用目标函数。

测试代码

#include <any>
#include <iostream>
#include <map>

class VarCall {
   public:
    VarCall()
        : calls({{"Alice", std::any(&VarCall::foo)},
                 {"Bob", std::any(&VarCall::bar)},
                 {"Charlie", std::any(&VarCall::baz)}}) {}

    template <typename... Args>
    void call(const std::string& what, Args... args) {
        void (VarCall::*ptr)(Args...);
        std::any a = ptr;
        std::cout << a.type().name() << std::endl;
        std::cout << calls[what].type().name() << std::endl;
        // 调用尝试失败
        // this->*(std::any_cast<decltype(ptr)>(calls[what]))(args...);
    }

   public:
    void foo() { std::cout << "foo()" << std::endl; }
    void bar(const std::string& s) {
        std::cout << "bar(" << s << ")" << std::endl;
    }
    void baz(int i) { std::cout << "baz(" << i << ")" << std::endl; }

    std::map<std::string, std::any> calls;
};

int main() {
    VarCall v;

    void (VarCall::*ptr)(const std::string& s);
    std::any a = ptr;
    std::any b(&VarCall::bar);
    std::cout << a.type().name() << std::endl;
    std::cout << b.type().name() << std::endl;

    // v.call("Alice");
    v.call("Bob", "2");
    // v.call("Charlie", 1);

    return 0;
}

预期输出

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

实际输出

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
M7VarCallFvPKcE
M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

原因分析

  1. 模板参数推导的自动匹配:调用v.call("Bob", "2")时,字符串字面量"2"的类型是const char*,模板参数Args...会被自动推导为const char*,因此call函数内的ptr类型是void (VarCall::*)(const char*),对应输出中的M7VarCallFvPKcE。
  2. 存储阶段的类型正确性:构造函数中存入std::any的&VarCall::bar类型是void (VarCall::*)(const std::string&),这与main函数中b.type().name()的输出一致,说明存储阶段的类型是正确的。
  3. 类型不匹配导致调用失败:std::any_cast要求目标类型与存储的类型完全一致,推导后的const char*版本指针与存储的const std::string&版本指针类型不兼容,因此无法完成转换和调用。

解决方案

方案一:显式指定模板参数

调用call时显式指定参数类型为const std::string&,让模板参数与存储的成员函数指针类型匹配:

v.call<const std::string&>("Bob", "2");

此时ptr的类型会被指定为void (VarCall::*)(const std::string&),与std::any中存储的类型一致,即可成功执行std::any_cast调用。

方案二:使用std::function包装成员函数

将成员函数包装为std::function存入映射,利用std::function的隐式参数转换能力(如const char*自动转为std::string),避免严格的类型匹配限制:

#include <any>
#include <iostream>
#include <map>
#include <functional>

class VarCall {
   public:
    VarCall()
        : calls({{"Alice", std::function<void(VarCall&)>([](VarCall& obj) { obj.foo(); })},
                 {"Bob", std::function<void(VarCall&, const std::string&)>([](VarCall& obj, const std::string& s) { obj.bar(s); })},
                 {"Charlie", std::function<void(VarCall&, int)>([](VarCall& obj, int i) { obj.baz(i); })}}) {}

    template <typename... Args>
    void call(const std::string& what, Args... args) {
        auto func = std::any_cast<std::function<void(VarCall&, Args...)>>(calls[what]);
        func(*this, std::forward<Args>(args)...);
    }

   public:
    void foo() { std::cout << "foo()" << std::endl; }
    void bar(const std::string& s) {
        std::cout << "bar(" << s << ")" << std::endl;
    }
    void baz(int i) { std::cout << "baz(" << i << ")" << std::endl; }

    std::map<std::string, std::any> calls;
};

int main() {
    VarCall v;
    v.call("Bob", "2"); // 自动将const char*转为std::string
    return 0;
}

这种方式更灵活,无需显式指定模板参数,std::function会自动处理参数的隐式转换,简化调用逻辑。

内容的提问来源于stack exchange,提问作者Oersted

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 00:50:11