boost::bind/function的索引占位符的实现

2024-04-08 10:08

本文主要是介绍boost::bind/function的索引占位符的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

boost::bind/function的索引占位符的实现

说明:如果代码中的对boost库的使用有误(如大小写不匹配,丢失连字符等),请勿奇怪。本文仅用于讲解,请把它们当成伪码。

所谓索引占位符,就是指在执行bind时,传入的参数不是具体的数值,而是一个能够起到索引作用的对象,该对象能够从传递给(bind函数返回的函数对象的)函数调用的实参中取得对应的数值,如:
int callback(int a, int b){return a + b;}
int result1 = bind(callback, 10, _1)(20);//callback(10, 20)
int result2 = bind(callback, _2, _1)(20, 10);//callback(10, 20)
上面表达式中的_1和_2就是占位符;对于int result2 = bind(callback, _2, _1)(20, 10);中的(20, 10),20在实参中的索引是1,10在实参中的索引是2,也就是说实参索引也就是实参的顺序,只不过该顺序从1开始。_1表示顺序号为1的实参,_2表示顺序号为2的实参。因此,在int result2 = bind(callback, _2, _1)(20, 10);中,_1对应于(20, 10)中的20,_2对应于(20, 10)中的10。
那么,_1、_2具体是如何进行索引的呢?
先看看_1、_2的定义(这些定义乃个人定义,并非来自boost,不过和boost类似):
template<int INDEX> struct TIndex{};
typedef TIndex<1> _1;
typedef TIndex<2> _2;
可以看到,_1、_2是类型;不过要注意,bind(callback, _2, _1)中的bind接受的是_1、_2对象,而不是_1、_2类型,它相当于:
_2 index2;
_1 index1;
bind(index2, index1);
在上一篇中,已经讲过,bind的返回值是一个函数对象。对于int result2 = bind(callback, _2, _1)(20, 10);这一表达式中的bind,其返回值类型类似于:
class TBoundFunctionObject
{
public:
 int operator()(int a, int b)
 {
  int parameter1 = ValueFromIndex(m_index1InParameters, a, b);
  int parameter2 = ValueFromIndex(m_index2InParameters, a, b);
  return m_callback(parameter1, parameter2);
 }
private:
 int (*m_callback)(int a, int b);
 TIndex<2> m_index1InParameters;
 TIndex<1> m_index2InParameters;
};
参考上一篇,将之改为模板:
template<typename TReturn, typename TArg1, typename TArg2, typename TIndex1, typename TIndex2> class TBoundFunctionObject
{
public:
 TReturn operator()(TArg1 a, TArg2 b)
 {
  TArg1 parameter1 = ValueFromIndex(m_index1InParameters, a, b);
  TArg2 parameter2 = ValueFromIndex(m_index2InParameters, a, b);
  return m_callback(parameter1, parameter2);
 }
private:
 TReturn (*m_callback)(TArg1 a, TArg2 b);
 TIndex1 m_index1InParameters;
 TIndex2 m_index2InParameters;
};
不同之处在于,该模板增加了表示索引占位符类型的模板参数TIndex1和TIndex2。其实这么说是不完整的,因为传递给bind的即可以是索引占位符,也可以是实际数值。为此,将上面的模板稍作更改:
template<typename TReturn, typename TArg1, typename TArg2, typename TBind1, typename TBind2> class TBoundFunctionObject
{
public:
 TReturn operator()(TArg1 a, TArg2 b)
 {
  TArg1 parameter1 = ValueFromIndex(m_bind1, a, b);
  TArg2 parameter2 = ValueFromIndex(m_bind2, a, b);
  return m_callback(parameter1, parameter2);
 }
private:
 TReturn (*m_callback)(TArg1 a, TArg2 b);
 TBind1 m_bind1;
 TBind2 m_bind2;
};
其中TBind1既可以是实际数值类型,也可以是_1、_2等。
现在,关键的地方来了,ValueFromIndex如何实现?
可以肯定的是,ValueFromIndex是一个函数模板,其返回值由其第一个参数决定:
 如果是实际数值,则返回实际数值;
 如果是_1,则返回第二个参数a;
 如果是_2,则返回第三个参数b。
其实现应当类似于:
template<typename TArg1, typename TArg2, typename TArg3> TReturn ValueFromIndex(TArg1 a1, TArg2 a2, TArg3 a3)
{
 if(IsParameter(a1)) return a1;
 if(Is_1(a1)) return a2;
 if(Is_2(a1)) return a3;
 throw exception("invalid a1.");
}
问题来了:
1. TReturn该如何定义?
2. 如果TArg2、TArg3和TArg1(在表示实际数值时)不同,上面的实现显然非法,因为一个函数不可能具有多个返回值类型。那么,该如何解决?
两个问题的解决办法当然是有,需要利用模板元编程。
我们知道三目运算符?:,但它无法应用于类型,boost::mpl中有一个if实现了该功能:
boost::mpl::if<bool, Type1, Type2>::type:如果第一个参数为true,则type表示Type1,否则表示Type2
实现原理(利用偏特化):
template<bool T1, typename Type1, typename Type2> struct if
{
 typedef Type1 type;
};
template<typename Type1, typename Type2> struct if<false>
{
 typedef Type2 type;
};
还可以利用偏特化实现switch类型的功能:
switch<int i, char, short, int>::type:如果i为1,则type表示short;如果为2,则type表示int;否则(default),type表示char。
template<int i, typename Type1, typename Type2, typename Type3> struct switch
{
 typedef Type1 type;
};
template<typename Type1, typename Type2, typename Type3> struct switch<1>
{
 typedef Type2 type;
};
template<typename Type1, typename Type2, typename Type3> struct switch<2>
{
 typedef Type3 type;
};
仿照该switch,第一个问题解决了,TReturn应该如下定义:
TSwitch<TBind, TArg1, TArg2>::type
template<typename TBind, typename TArg1, typename TArg2> struct TSwitch
{
 typedef TBind type;
};
template<typename TArg1, typename TArg2> struct TSwitch<_1>
{
 typedef TArg1 type;
};
template<typename TArg1, typename TArg2> struct TSwitch<_2>
{
 typedef TArg2 type;
};
对于第二个问题,方法类似:
template<typename TArg1, typename TArg2, typename TArg3> TSwitch<TArg1, TArg2, TArg3>::type ValueFromIndex(TArg1 a1, TArg2 a2, TArg3 a3)
{
 return a1;
}
template<typename TArg2, typename TArg3> TSwitch<_1, TArg2, TArg3>::type ValueFromIndex(_1 a1, TArg2 a2, TArg3 a3)
{
 return a2;
}
template<typename TArg2, typename TArg3> TSwitch<_2, TArg2, TArg3>::type ValueFromIndex(_2 a1, TArg2 a2, TArg3 a3)
{
 return a3;
}
但是很可惜,模板函数不支持偏特化,所以需要包装一下:
template<typename TArg1, typename TArg2, typename TArg3> struct ValueFromIndex
{
 static TSwitch<TArg1, TArg2, TArg3>::type Value(TArg1 a1, TArg2 a2, TArg3 a3)
 {
  return a1;
 }
};
template<typename TArg2, typename TArg3> struct ValueFromIndex<_1>
{
 static TSwitch<_1, TArg2, TArg3>::type Value(_1 a1, TArg2 a2, TArg3 a3)
 {
  return a2;
 }
};
template<typename TArg2, typename TArg3> struct ValueFromIndex<_2>
{
 static TSwitch<_2, TArg2, TArg3>::type Value(_2 a1, TArg2 a2, TArg3 a3)
 {
  return a3;
 }
};
相应修改TBoundFunctionObject:
template<typename TReturn, typename TArg1, typename TArg2, typename TBind1, typename TBind2> class TBoundFunctionObject
{
public:
 TReturn operator()(TArg1 a, TArg2 b)
 {
  TArg1 parameter1 = ValueFromIndex<TBind1, TArg1, TArg2>::Value(m_bind1, a, b);
  TArg2 parameter2 = ValueFromIndex<TBind2, TArg1, TArg2>::Value(m_bind2, a, b);
  return m_callback(parameter1, parameter2);
 }
private:
 TReturn (*m_callback)(TArg1 a, TArg2 b);
 TBind1 m_bind1;
 TBind2 m_bind2;
};
最后,还有一个问题需要注意,就是TBoundFunctionObject::operator()能够接受的参数数目需要随着传递给bind的实际数值的个数作出变动,如:
int result1 = bind(callback, 10, _1)(20);//TBoundFunctionObject::operator()必须接受1个参数
int result2 = bind(callback, _2, _1)(20, 10);//BoundFunctionObject::operator()必须接受2个参数
int result2 = bind(callback, _2, _2)(20, 10);//BoundFunctionObject::operator()必须接受2个参数(此处不符合boost中的规定,跳过_1直接使用了_2)
一个简单的解决办法是重载operator(),列出所有可能的情况,然后在各种情况中进行相应的判断
template<typename TReturn, typename TArg1, typename TArg2, typename TBind1, typename TBind2> class TBoundFunctionObject
{
public:
 TReturn operator()()//bind(callback, 10, 20)();
 {
  assert(TMaxIndex<TBind1, TBind2>::Value == 0);
  return m_callback(m_bind1, m_bind2);
 }
 template<tpyename TArg> TReturn operator()(TArg a)//bind(callback, 10, _1)(20);bind(callback, _1, 20)(10);bind(callback, _1, _1)(20);
 {
  assert(TMaxIndex<TBind1, TBind2>::Value == 1);
  TArg1 parameter1 = ValueFromIndex2<TBind1, TArg>::Value(m_bind1, a);
  TArg2 parameter2 = ValueFromIndex2<TBind2, TArg>::Value(m_bind2, a);
  return m_callback(parameter1, parameter2);
 }
 TReturn operator()(TArg1 a, TArg2 b)//bind(callback, _2, _1)(20, 10);bind(callback, _2, _2)(20, 10);
 {
  //如果要禁止bind(callback, _2, _2)(20, 10);,可以增加assert;不过该写法太繁琐,较好的方法参考下一篇
  //assert((TMaxIndex<TBind1>::Value == 1 && TMaxIndex<TBind1>::Value == 2) || (TMaxIndex<TBind1>::Value == 2 && TMaxIndex<TBind1>::Value == 1));
  assert(TMaxIndex<TBind1, TBind2>::Value == 2);
  TArg1 parameter1 = ValueFromIndex<TBind1, TArg1, TArg2>::Value(m_bind1, a, b);
  TArg2 parameter2 = ValueFromIndex<TBind2, TArg1, TArg2>::Value(m_bind2, a, b);
  return m_callback(parameter1, parameter2);
 }
};
ValueFromIndex2类似ValueFromIndex,但只接受两个参数:如果第一个参数是_1,返回第二参数;否则返回第一个参数
TMaxIndex<>:用来取得最大的占位符的最大值;如TMaxIndex<_1,_3,_2>::Value为3;TMaxIndex<SomeType>::Value为0
TMaxIndex的实现:
template<bool b, int i1, int i2> struct IfValue
{
 enum enumValue{Value = i1};
};
template<int i1, int i2> struct IfValue<false>
{
 enum enumValue{Value = i2};
};
template<int i1, int i2> struct TMax : public IfValue<(i1 > i2), i1, i2>
{
};

//一个参数版本的TMaxIndex
template<typename TType> struct TMaxIndex1
{
 enum enumValue{Value = 0};
};
template<> struct TMaxIndex1<_1>
{
 enum enumValue{Value = 1};
};
template<> struct TMaxIndex1<_2>
{
 enum enumValue{Value = 2};
};
//两个参数版本的TMaxIndex
template<typename TType1, typename TType2> struct TMaxIndex2
{
 enum enumValue{Value = TMax< TMaxIndex1<TType1>::Value, TMaxIndex1<TType2>::Value >::Value;
};
//三个参数版本的TMaxIndex
template<typename TType1, typename TType2, typename TType3> struct TMaxIndex3
{
 enum enumValue{Value = TMax< TMaxIndex2<TType1, TType2>::Value, TMaxIndex1<TType3>::Value >::Value;
};
//四个参数版本的TMaxIndex
template<typename TType1, typename TType2, typename TType3, typename TType4> struct TMaxIndex4
{
 enum enumValue{Value = TMax< TMaxIndex3<TType1, TType2, TType3>::Value, TMaxIndex1<TType4>::Value >::Value;
};
上面的TBoundFunctionObject还不够好:无法在编译期检测出调用参数数目错误的operator();对此,可以将assert(TMaxIndex<TBind1, TBind2>::Value == 0);更改为BOOST_STATIC_ASSERT(TMaxIndex<TBind1, TBind2>::Value == 0)等。
如果不允许重载operator(),该如何解决?留给大家思考吧。如果没有什么头绪,可以参考下几篇文章。

 

这篇关于boost::bind/function的索引占位符的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/885281

相关文章

MySQL逻辑删除与唯一索引冲突解决方案

《MySQL逻辑删除与唯一索引冲突解决方案》本文探讨MySQL逻辑删除与唯一索引冲突问题,提出四种解决方案:复合索引+时间戳、修改唯一字段、历史表、业务层校验,推荐方案1和方案3,适用于不同场景,感兴... 目录问题背景问题复现解决方案解决方案1.复合唯一索引 + 时间戳删除字段解决方案2:删除后修改唯一字

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u