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

C++重载函数调用歧义求助:std::function子类化后的重载问题

问题:std::function子类化后重载函数调用歧义

我在研究C++的std::function对象时,想要为其添加一些功能,因此对它进行了子类化处理。然而,当我尝试编写一个基于传入函数签名进行重载的doThing函数时,编译器提示函数调用存在歧义。请帮忙解释歧义产生的原因,以及我该如何消除这种歧义?

示例代码

#include <functional>
#include <iostream>

class A0 : public std::function<void()>
{
public:
    template <typename T>
    A0(const T& t)
        : std::function<void()> { t }
    {
    }
};

template <typename T1>
class A1 : public std::function<void(T1)>
{
public:
    template <typename T>
    A1(const T& t)
        : std::function<void(T1)> { t }
    {
    }
};

void doThing(const A0& a0)
{
    std::cout << "Do A0 thing\n";
}

void doThing(const A1<int>& a1)
{
    std::cout << "Do A1 thing\n";
}

int main()
{
    doThing([] (int i) {});

    return 0;
}

编译器错误输出

g++ .\sources\Sample.cpp            
.\sources\Sample.cpp: In function 'int main()':
.\sources\Sample.cpp:37:12: error: call of overloaded 'doThing(main()::<lambda(int)>)' is ambiguous
   37 |     doThing([] (int i) {});
      |     ~~~~~~~^~~~~~~~~~~~~~~
.\sources\Sample.cpp:25:6: note: candidate: 'void doThing(const A0&)'
   25 | void doThing(const A0& a0)
      |      ^~~~~~~
.\sources\Sample.cpp:30:6: note: candidate: 'void doThing(const A1<int>&)'
   30 | void doThing(const A1<int>& a1)
      |      ^~~~~~~

歧义产生的原因

  1. 构造函数的通用性:A0和A1<int>都包含模板构造函数,能接受任意类型的参数,随后尝试将其转换为对应的std::function基类类型。
  2. std::function的宽松转换规则:std::function<void()>允许接收带多余参数的可调用对象——调用时会自动忽略多余的参数。因此,你的带int参数的lambda既可以被转换为std::function<void()>(进而构造A0),也可以被转换为std::function<void(int)>(进而构造A1<int>)。
  3. 转换优先级无差异:这两个转换都属于用户定义的转换序列,编译器无法判定哪个更符合预期,因此报出歧义错误。

消除歧义的方法

方法1:限制构造函数的转换范围

通过SFINAE技术,让A0和A1的构造函数只接受能精确匹配目标std::function签名的可调用对象,阻断不必要的转换路径:

#include <functional>
#include <iostream>
#include <type_traits>

class A0 : public std::function<void()>
{
public:
    // 仅允许能转换为std::function<void()>的参数
    template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::function<void()>>>>
    A0(const T& t)
        : std::function<void()>{t}
    {
    }
};

template <typename T1>
class A1 : public std::function<void(T1)>
{
public:
    // 仅允许能转换为std::function<void(T1)>的参数
    template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::function<void(T1)>>>>
    A1(const T& t)
        : std::function<void(T1)>{t}
    {
    }
};

void doThing(const A0& a0)
{
    std::cout << "Do A0 thing\n";
}

void doThing(const A1<int>& a1)
{
    std::cout << "Do A1 thing\n";
}

int main()
{
    doThing([] (int i) {}); // 仅匹配A1<int>重载
    doThing([] () {});       // 仅匹配A0重载
    return 0;
}

方法2:显式构造目标类型

如果不想修改A0和A1的定义,可以在调用doThing时显式指定要构造的对象类型,跳过编译器的自动转换选择:

int main()
{
    doThing(A1<int>{[] (int i) {}}); // 明确指定构造A1<int>
    return 0;
}

方法3:使用组合而非继承(推荐)

std::function没有虚析构函数,子类化它存在风险(比如通过基类指针删除子类对象会导致未定义行为)。更安全的方式是把std::function作为成员变量,通过组合来扩展功能:

#include <functional>
#include <iostream>
#include <type_traits>

class A0
{
private:
    std::function<void()> func;
public:
    template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::function<void()>>>>
    A0(const T& t) : func(t) {}
    
    // 保留std::function的调用行为
    void operator()() const { func(); }
    // 在这里添加你的扩展功能
};

template <typename T1>
class A1
{
private:
    std::function<void(T1)> func;
public:
    template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::function<void(T1)>>>>
    A1(const T& t) : func(t) {}
    
    void operator()(T1 arg) const { func(arg); }
    // 添加你的扩展功能
};

void doThing(const A0& a0)
{
    std::cout << "Do A0 thing\n";
}

void doThing(const A1<int>& a1)
{
    std::cout << "Do A1 thing\n";
}

int main()
{
    doThing([] (int i) {});
    doThing([] () {});
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 14:53:14