刚刚看到一篇博客,说 std::bind 无法绑定正确的重载函数。这里的问题并不是 std::bind 能力不足,而是将函数名传递给 std::bind 时编译器无法取到这个函数的地址(也就是符号,编译器会先解析成符号,链接器再替换为地址),因为有多个重载函数都是这个名字。核心问题是无法通过函数名取到想要的重载函数地址。就像下面的代码无法编译通过:

#include <iostream>

void f()
{
std::cout << "f 1" << std::endl;
} void f(int x)
{
std::cout << "f 2 " << x << std::endl;
} int main()
{
auto p = &f;
}

编译错误:

/home/abc/cpplearn/overload_func.cpp: In function ‘int main()’:
/home/abc/cpplearn/overload_func.cpp:15:15: error: unable to deduce ‘auto’ from ‘& f’
15 | auto p = &f;
| ^
/home/abc/cpplearn/overload_func.cpp:15:15: note: couldn’t deduce template parameter ‘auto’

有没有什么比较完美的解决办法呢?我觉得一定有,因为 C 语言没有函数重载,函数地址作为实参也是常规操作。相比之下,C++ 引入了函数重载,却无法取到函数地址,这就很尴尬。C++ 设计者肯定也想到了这个问题。

于是查阅了 cppreference.com,看到了 Address of an overloaded function。函数名的重载解析除了发生在函数调用的时候,也会发生在以下 7 种语境:

# Context Target
1 initializer in a declaration of an object or reference the object or reference being initialized
2 on the right-hand-side of an assignment expression the left-hand side of the assignment
3 as a function call argument the function parameter
4 as a user-defined operator argument the operator parameter
5 the return statement the return type of a function
6 explicit cast or static_cast argument the target type of a cast
7 non-type template argument the type of the template parameter

当函数名存在于这 7 种语境时,会发生重载解析,并且会选择与 Target 类型匹配的那个重载函数。这里就不一一考察这 7 种语境了,有兴趣可以自己查阅 cppreference.com。这里重点考察第 3 种和第 6 种。

先看第 3 种语境。当函数名作为函数调用的实参时,重载解析会选择和形参类型相匹配的版本。也就是说,下面的代码会如期运行:

#include <iostream>

void f()
{
std::cout << "f 1" << std::endl;
} void f(int x)
{
std::cout << "f 2 " << x << std::endl;
} void call(void p(int)) {
p(1);
} int main()
{
call(f);
}

这段代码输出:

f 2 1

回到最初的问题,std::bind 也是函数,为什么无法正常编译呢?直接分析一下面代码的编译错误信息:

#include <iostream>
#include <functional> void f()
{
std::cout << "f 1" << std::endl;
} void f(int x)
{
std::cout << "f 2 " << x << std::endl;
} int main()
{
auto new_func = std::bind(f, std::placeholders::_1);
new_func(66);
}

编译错误:

/home/abc/cpplearn/overload_func.cpp: In function ‘int main()’:
/home/abc/cpplearn/overload_func.cpp:16:30: error: no matching function for call to ‘bind(<unresolved overloaded function type>, const std::_Placeholder<1>&)’
16 | auto new_func = std::bind(f, std::placeholders::_1);
| ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~

可以看到,std::bind 准确地说是一个函数模板。它要根据其参数进行模板实参推导,再替换模板形参进行实例化(Instantiation),产生和普通函数类似的汇编代码。std::bind 进行实例化的时候,函数 f 还没有进行重载解析,其类型为<unresolved overloaded function type>。std::bind 无法进行实例化。怎样修改可以解决这个问题呢?

可以利用第 6 个语境,也就是显示转换或 static_cast。重载解析会选择与它们的目标类型相匹配的版本。下面的代码会如期运行:

#include <iostream>
#include <functional> void f()
{
std::cout << "f 1" << std::endl;
} void f(int x)
{
std::cout << "f 2 " << x << std::endl;
} int main()
{
auto new_func = std::bind((void(*)(int))f, std::placeholders::_1);
new_func(66);
}

这段代码输出:

f 2 66

还有一种更加巧妙的办法,依然是利用第 3 种语境。既然隐式实例化会进行模板实参推导,和重载解析相矛盾。为什么不直接解决这个矛盾,将隐式实例化改为显示实例化?来看下面的代码:

#include <iostream>
#include <functional> void f()
{
std::cout << "f 1" << std::endl;
} void f(int x)
{
std::cout << "f 2 " << x << std::endl;
} int main()
{
auto new_func = std::bind<void(int)>(f, std::placeholders::_1);
new_func(66);
}

这段代码如期输出:

f 2 66

C++ 获取指定的重载函数地址的更多相关文章

  1. JAVA 获取指定网址的IP地址 实例

    如今买票是一大难事,在高峰时段 打开12306网站,慢的像蜗牛,想到以前用修改hosts文件来登录Google(Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开,其作用就是将一些常用的网址 ...

  2. Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取

    为什么要写这篇文章 1.      因为最近在学习<软件调试>这本书,看到书中的某个调试历程中讲了Windows的系统调用的实现机制,其中讲到了从Ring3跳转到Ring0之后直接进入了K ...

  3. 获取C++类成员虚函数地址

    1.GCC平台 GCC平台获取C++成员虚函数地址可使用如下方法[1]: class Base{ int i; public: virtual void f1(){ cout<<" ...

  4. 函数用途:同一域名对应多个IP时,获取指定服务器的远程网页内容

    <?php /************************ * 函数用途:同一域名对应多个IP时,获取指定服务器的远程网页内容 * 创建时间:2008-12-09 * 创建人:张宴(img. ...

  5. 旧书重温:0day2【4】动态获取函数地址

    通过以上3篇文章的学习,我们已经可以获取到kernel32.dll的地址了下一步 我们就是获取几个重要的函数 1.GetProcAddress 2.LoadLibrary 有了这两个函数很多函数都可以 ...

  6. 【逆向篇】分析一段简单的ShellCode——从TEB到函数地址获取

    其实分在逆向篇不太合适,因为并没有逆向什么程序. 在http://www.exploit-db.com/exploits/28996/上看到这么一段最简单的ShellCode,其中的技术也是比较常见的 ...

  7. 告别硬编码-发个获取未导出函数地址的Dll及源码

    还在为找内核未导出函数地址而苦恼嘛? 还在为硬编码通用性差而不爽吗? 还在为暴搜内核老蓝屏而痛苦吗? 请看这里: 最近老要用到内核未导出的函数及一些结构,不想再找特征码了,准备到网上找点符号文件解析的 ...

  8. C#获取指定IP地址的数据库所有数据库实例名

    /// <summary> /// 获取指定IP地址的数据库所有数据库实例名. /// </summary> /// <param name="ip" ...

  9. JAVA获取指定的类型的本机MAC地址

    前面我们运维小伙在部署的时候,发现在真实服务器获取不到mac地址或者获取不到指定类型的mac地址,写程序记录如下 import com.google.common.base.Strings; impo ...

随机推荐

  1. table表格做出圆角效果

    采用border-radius 这个属性的时候在chrome里面没有圆角,倒是在IE里面有圆角. 不知道是不是没有写webkie 前缀,但是加上一段神奇的代码overflow:hidden的是时候在c ...

  2. Qt QPropertyAnimation+QTimer实现自制悬浮窗

    目录 Qt下的悬浮窗 QPropertyAnimation QTimer 事件过滤 图标变换 自适应窗口大小 使用方法 Qt下的悬浮窗 最近项目需要一个类似于360悬浮球类似的悬浮窗,当鼠标放入停留一 ...

  3. VUE3 之 全局 Mixin 与 自定义属性合并策略 - 这个系列的教程通俗易懂,适合新手

    1. 概述 老话说的好:心态决定命运,好心态才能有好的命运. 言归正传,今天我们来聊聊 VUE 中的全局 Mixin 与 自定义属性合并策略. 2. Mixin 的使用 2.1 全局 Mixin 之前 ...

  4. 控制反转 IOC 理论推导

    控制反转 IOC 理论推导 按照我们传统的开发,我们会先去 dao 层创建一个接口,在接口中定义方法. public interface UserDao { void getUser(); } 然后再 ...

  5. python---两个栈实现一个队列

    class Solution: """两个栈实现一个队列""" def __init__(self): # 接收栈 self.accept_ ...

  6. Struts2-向值栈中存放数据

    1.第一种 获取值栈对象,调用值栈对象里面的set方法(该方法添加的是一个Map集合) //第一种方式,使用值栈对象获取对象里面的set方法 //1.获取值栈对象 ActionContext cont ...

  7. linux压缩打包、定时任务

    压缩打包 gzip压缩 win中的压缩包:zip rar Linux常见的压缩包有哪些? gzip bzip2 1.gzip压缩 压缩命令:gzip [压缩文件] 解压命令:gzip -d [压缩包] ...

  8. Ubu18开机自启动

    Ubu开机自启动 简单示例 在/etc/init.d/目录下新建启动脚本Test #!/bin/bash ### BEGIN INIT INFO # Provides: Test # Required ...

  9. android软件简约记账app开发day07-备注界面完善

    android软件简约记账app开发day07-备注界面完善 ## 昨天我们已经绘制了备注页面,今天来用Java代码组装完善一下. 首先我们新建BeiZhuDialog类关联备注页面,并且实现点击接口 ...

  10. 4.25JMster环境搭建、webxml及测试平台练习

    1.Java环境搭建 右击电脑属性--高级设置--环境变量--系统变量--新建(输入JAVA_HOME.C:\Program Files\Java\jdk1.8.0_91---CLASSPATH..; ...