C++模板:编译时模拟Duck Typing

 
C++泛型与多态(4): Duck Typing - 简书

 

James Whitcomb Riley在描述这种is-a的哲学时,使用了所谓的鸭子测试Duck Test):

当我看到一只鸟走路像鸭子,游泳像鸭子,叫声像鸭子,那我就把它叫做鸭子。(When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.)

鸭子测试

鸭子测试

Duck Typing不是动态语言的专利。C++作为一门强类型的静态语言,也对此特性有着强有力的支持。只不过,这种支持不是运行时,而是编译时。

其实现的方式为:一个模板类或模版函数,会要求其实例化的类型必须具备某种特征,如某个函数签名,某个类型定义,某个成员变量等等。如果特征不具备,编译器会报错。

通过之前的解释我们不难发现,Duck Typing要表达的多态语义如下图所示:

DuckTyping的语义

DuckTyping的语义

适配器:类型萃取

Duck Typing需要实例化的类型具备一致的特征,而模板特化的作用正是为了让不同类型具有统一的特征(统一的操作界面),所以模板特化可以作为Duck Typing与实例化类型之间的适配器。这种模板特化手段称为萃取Traits),其中类型萃取最为常见,毕竟类型是模板元编程的核心元素。

所以,类型萃取首先是一种非侵入性的中间层。否则,这些特征就必须被实例化类型提供,而就意味着,当一个实例化类型需要复用多个Duck Typing模板时,就需要迎合多种特征,从而让自己经常被修改,并逐渐变得庞大和难以理解。

Type Traits的语义

Type Traits的语义

另外,一个Duck Typing模板,比如一个通用算法,需要实例化类型提供一些特征时,如果一个类型是类,则是一件很容易的事情,因为你可以在一个类里定义任何需要的特征。但如果一个基本类型也想复用此通用算法,由于基本类型无法靠自己提供算法所需要的特征,就必须借助于类型萃取

结论

这四篇文章所介绍的,就是C++泛型编程的全部关键知识。

从中可以看出,泛型是一种多态技术。而多态的核心目的是为了消除重复隔离变化,提高系统的正交性。因而,泛型编程不仅不应该被看做奇技淫巧,而是任何一个追求高效的C++工程师都应该掌握的技术。

同时,我们也可以看出,相关的思想在其它范式和语言中(FP,动态语言)也都存在。因而,对于其它范式和语言的学习,也会有助于更加深刻的理解泛型,从而正确的使用范型。

最后给出关于泛型的缺点:

  1. 复杂模板的代码非常难以理解;
  2. 编译器关于模板的出错信息十分晦涩,尤其当模板存在嵌套时;
  3. 模板实例化会进行代码生成,重复信息会被多次生成,这可能会造成目标代码膨胀;
  4. 模板的编译可能非常耗时;
  5. 编译器对模板的复杂性往往会有自己限制,比如当使用递归时,当递归层次太深,编译器将无法编译;
  6. 不同编译器(包括不同版本)之间对于模板的支持程度不一,当存在移植性需求时,可能出现问题;
  7. 模板具有传染性,往往一处选择模板,很多地方也必须跟着使用模板,这会恶化之前的提到的所有问题。

这篇作者对此的原则是:在使用其它非泛型技术可以同等解决的前提下,就不会选择泛型。



作者:_袁英杰_
链接:https://www.jianshu.com/p/4939c934e160
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

template <typename T>
void f(T& object)
{object.f(0); // 要求类型 T 必须有一个可让此语句编译通过的函数。
}//换成下面版本,会出现编译错误。主要是C1和C4编不过
//template <typename T>
//void f(T& object)
//{
//    int result = object.f(0);
//    std::ignore = result;
//    // ... 
//}struct C1
{void f(int i){++i;std::cout << R"(f(int))" << std::endl;return ;}
};struct C2
{int f(char){std::cout << R"(f(char))" << std::endl;return 2;}
};struct C3
{int f(unsigned short, bool isValid = true){std::cout << R"(f(unsigned short, bool isValid = true))" << std::endl;return 3;}
};struct C4
{struct Object{};struct Foo{};Foo* f(Object*){std::cout << R"(f(Object*))" << std::endl;return NULL;}
};//void f(C1& object) 
//{
//    object.f(0); // 要求类型 T 必须有一个可让此语句编译通过的函数。
//}int main()
{C1 o1;C2 o2;C3 o3;C4 o4;f(o1);f(o2);f(o3);f(o4);return 1;
}输出:
f(int)
f(char)
f(unsigned short, bool isValid = true)
f(Object*)

 

Duck Typing with C++ templates

class foo
{
public:std::string as_string() const{return "i am foo";}
};class baz
{
public:// look ma, no 'as_string'
};template <typename T> class repr_type
{
public:repr_type(const T& o) : m_o(o){}std::string as_string() const{return call_as_string<T>(nullptr);}private:template <class C> std::string call_as_string(decltype(&C::as_string)) const{return m_o.as_string();}template <class C> std::string call_as_string(...) const{return "print pointer";//return string::format("%p", &m_o);}const T& m_o;
};template <typename T> 
std::string as_string(const T& o)
{return repr_type<T>(o).as_string();
}int main() 
{foo o;as_string(o);baz b;as_string(b);return 1;
}
对一个类进行增强class foo
{
public:std::string as_string() const{return "i am foo";}
};class baz
{
public:// look ma, no 'as_string'
};template <typename T> class repr_type
{
public:repr_type(const T& o) : m_o(o){}std::string as_string() const{return call_as_string<T>(nullptr);}private:template <class C> std::string call_as_string(decltype(&C::as_string)) const{return m_o.as_string();}template <class C> std::string call_as_string(...) const{return "print pointer";//return string::format("%p", &m_o);}const T& m_o;
};template <typename T> 
std::string as_string(const T& o)
{return repr_type<T>(o).as_string();
}class spreadsheet
{
public:void set(int x, int y, const char* text){//给表格中的一个单元设置内容std::cout << "hello" << std::endl;}//相当于以一种简介的接口对各种数据类型进行接收,没有as_string的就不做任何事情template <typename T> void set(int x, int y, const T& instance){set(x, y, as_string(instance).c_str());}
};int main() 
{spreadsheet s;foo obj;s.set(0, 0, obj); // where i could be "anything"return 1;
}

Duck Typing with C++ templates

So, folks, what is this Duck Typing thingy? Trusty wikipedia says: In duck typing, an object's suitability is determined by the presence of certain methods and properties (with appropriate meaning), rather than the actual type of the object. Let's take a look at a concrete example from a pet library of mine.

Often in code - especially often when logging stuff - you want to represent things as a string to be printed. A useful start is to use function overloading, mainly because there are some types of things that you cannot really extend. For example, you could have something like this:

    inline std::string as_string(const char* p){return p ? p : "<nullptr>";}inline std::string as_string(bool value){return value ? "true" : "false";}inline std::string as_string(int32_t number){return string::format("%d", number);}

The nice thing of this is: you can type as_string(x) and for many things, you will get out a useful string representation without having to care what the actual object is.

Enter objects. For many of these, the above approach will work as well:

    inline std::string as_string(const std::vector<std::string> v){return string::format("vector<string> with %d objects", (int) v.size());}inline std::string as_string(const my_elaborate_class& c){return string::format("my_elaborate_class at %p", &c);}

Now, maybe you want to have more information in as_string, and maybe you need to be able to access private data members of the objects. This can be solved by introducing a member function as_string, starting with this design:

    class foo{public:std::string as_string() const;};std::string as_string(const foo& x){return x.as_string();}class bar{public:std::string as_string() const;};std::string as_string(const bar& x){return x.as_string();}

This works only if you write separate as_string overloads for each class that implements as_string members, a rather pointless task. Nothing for us lazy programmer folk!

You could define an interface and force all objects to inherit it:

    interface can_be_represented_as_string{virtual std::string as_string() const = 0;};class foo : public can_be_represented_as_string{virtual std::string as_string() const override;};class bar : public can_be_represented_as_string{virtual std::string as_string() const override;};std::string as_string(const can_be_represented_as_string& something){return something.as_string();}

OK, nice! Now you can throw almost anything at as_string, and get a string back. This works, but it requires you to define inheritance on all your objects, which is an eyesore and anyway may sometimes not be feasible, because you may have objects that you don't have control over (or you already have a large hierarchy of objects and people would complain if you'd start messing up their inheritances). Enter duck typing

As written at the beginning, in duck typing, an object's suitability is determined by the presence of certain methods and properties (with appropriate meaning), rather than the actual type of the object

Looking back where we started:

    class foo{public:std::string as_string() const;};class bar{public:std::string as_string() const;};

Both foo and bar are objects that have methods named as_string with appropriate meaning, but the types are different. What can we do about that? This:

    template <typename T> std::string as_string(const T& o){return o.as_string();}

So this is a generic method that works because there are methods with the proper meaning - not because the type has inherited something special. Nice!

OK, but now what about objects that do no implement a method as_string with appropriate meaning? It would be nice if we could say something like

  1. If there is a as_string overload, use that
  2. If the object has as_string(), use that
  3. Otherwise, default to just dumping the address of the object

At this point, the first two work, but the third one is causing a headache. It turns out that there is a SFINAE based idiom to detect if a type has a member but that is only half the solution: most examples just end up in a template function that can be used to detect the fact, but not really do something about it. To see the problem, let's assume you've followed the examples I've linked to and have a template has_as_string<type>::value. And you have the following setup:

    class foo{public:std::string as_string() const;};class baz{public:// look ma, no 'as_string'};...static_assert(has_as_string<foo>::type, "");static_assert(!has_as_string<baz>::type, "");...template <typename T> std::string as_string(const T& o){if(has_as_string<T>::value)return o.as_string();return string::format("%p", &o);}

The static-asserts will work (provided your implementation is correct), but the as_string template won't, because it will attempt to generate code that uses as_string even if the object doesn't have the method. So you need something more involved:

    template <typename T> class repr_type{public:repr_type(const T& o):m_o(o){}std::string as_string() const{return call_as_string<T>(nullptr);}private:template <class C> std::string call_as_string(decltype(&C::as_string)) const{return m_o.as_string();}template <class C> std::string call_as_string(...) const{return string::format("%p", &m_o);}const T& m_o;};template <typename T> std::string as_string(const T& o){return repr_type<T>(o).as_string();}

It is worth looking at the fine print on this one:

  • Based on whether the type passed has a method as_string, this template will print either the result of that function, or simply the objects address.
  • Note the template function call_as_string: the template resolver will prefer the more specific type, and it will be able to use the first template if the address of as_string can be taken.
  • Note that because this is a template function, it is not compiled until it is actually used: so for things that don't have as_string a call to as_string will never be issued.
  • Also note for this to work, I cannot also pass in the object instance: so I need a proxy object - repr_type template - to hold a reference to the object while letting SFINAE do its work

So what use is any of this?

Well, assume you have a set of as_string thingies as discussed here: functions using overloading, some template magic and so on. All is fine and well and one day you start writing a method that takes strings. For example, assume you have a spreadsheet and you want to fill it with text:

    class spreadsheet{public:void set(int x, int y, const char* text);};

Now for most of the things you have, you can write

    spreadsheet s;...s.set(0,0,as_string(i)); // where i could be "anything"

But wait, it gets better: let's enhance the spreadsheet class:

    class spreadsheet{public:void set(int x, int y, const char* text);template <typename T> void set(int x, int y, const T& instance){set(x,y, as_string(instance));}};

This will result in even more clean client code:

    spreadsheet s;...s.set(0,0,i); // where i could be "anything"

Personally, I like my client code as readable as possible. I know that can be hard, because C++ tries really hard to be ugly and uglier still, but sometimes after years disparate features like overloading and templates and improved rules in C++11 work together to make the code look simple. Nice!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/495704.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【求职面试】驾照的种类

大型客车 A1 大型载客汽车 A3、B1、B2、C1、C2、C3、C4、M 牵引车 A2 重型、中型全挂、半挂汽车列车 B1、B2、C1、C2、C3、C4、M 城市公交车 A3 核载10人以上的城市公共汽车 C1、C2、C3、C4 中型客车 B1 中型载客汽车&#xff08;10人以上、19人以下&#xff09; C1、C2、C3…

PyQt实战——使用python提取JSON数据(十)

系类往期文章&#xff1a; PyQt5实战——多脚本集合包&#xff0c;前言与环境配置&#xff08;一&#xff09; PyQt5实战——多脚本集合包&#xff0c;UI以及工程布局&#xff08;二&#xff09; PyQt5实战——多脚本集合包&#xff0c;程序入口QMainWindow&#xff08;三&…

RAG实战:构建基于本地大模型的智能问答系统

RAG实战&#xff1a;构建基于本地大模型的智能问答系统 引言 在当今AI快速发展的时代&#xff0c;如何构建一个既智能又可靠的问答系统是一个重要课题。本文将介绍如何使用RAG&#xff08;检索增强生成&#xff09;技术&#xff0c;结合本地大模型&#xff0c;构建一个高效的智…

OAuth 2.0

简介 OAuth 是一种开放标准的授权协议或框架&#xff0c;它提供了一种安全的方式&#xff0c;使第三方应用程序能够访问用户在其他服务上的受保护资源&#xff0c;而无需共享用户的凭证&#xff08;如用户名和密码&#xff09;。OAuth 的核心思想是通过“授权令牌”来代替直接…

IntelliJ IDEA 远程调试

IntelliJ IDEA 远程调试 在平时开发 JAVA 程序时&#xff0c;在遇到比较棘手的 Bug 或者是线上线下结果不一致的情况下&#xff0c;我们会通过打 Log 或者 Debug 的方式去定位并解决问题&#xff0c;两种方式各有利弊&#xff0c;今天就简要介绍下如何通过远程 Debug 的情况下…

美国辅料查询之FDA批准药用辅料数据库(IID数据库)

药用辅料的性质很大程度上决定了制剂的性质&#xff0c;每一种新的药用辅料的问世&#xff0c;都会为制剂技术的发展带来新的机遇&#xff0c;每一种药用辅料都可能让制剂研发员开发出新剂型药物&#xff0c;所以在药物制剂研发过程中&#xff0c;药用辅料的信息调研是不可或缺…

YOLOv10目标检测-训练自己的数据

yolov10 https://github.com/THU-MIG/yolov10?tabreadme-ov-file 1. 数据集 模型的建立需要收集图片并且进行标注。YOLOv10标注的文件格式如下&#xff08;每张图片对应一个标签文件&#xff09;&#xff1a; 0 0.441753 0.815461 0.061021 0.042763 1 0.395895 0.759868 …

Redis学习(五)优惠券秒杀2——分布式锁

Redis学习&#xff08;五&#xff09;优惠券秒杀2 一、分布式锁-redission二、快速入门三、redission可重入锁原理四、redission锁的MutiLock原理 一、分布式锁-redission 基于setnx实现的分布式锁存在下面的问题&#xff1a; 重入问题&#xff1a;重入问题是指 获得锁的线程…

Flink调优----资源配置调优与状态及Checkpoint调优

目录 第 1 章 资源配置调优 1.1 内存设置 1.1.1 TaskManager 内存模型 1、内存模型详解 2、案例分析 1.1.2 生产资源配置示例 1.2 合理利用 cpu 资源 1.2.1 使用 DefaultResourceCalculator 策略 1.2.2 使用 DominantResourceCalculator 策略 1.2.3 使用 DominantRes…

Docker怎么关闭容器开机自启,批量好几个容器一起操作?

环境&#xff1a; WSL2 docker v25 问题描述&#xff1a; Docker怎么关闭容器开机自启&#xff0c;批量好几个容器一起操作&#xff1f; 解决方案&#xff1a; 在 Docker 中&#xff0c;您可以使用多种方法来关闭容器并配置它们是否在系统启动时自动启动。以下是具体步骤和…

模型的量化(Quantization)

文章目录 一、浮点数格式&#xff1a;FP64, FP32, FP16, BFLOAT16, TF32之间的相互区别1、关于浮点数2、常见的浮点数格式 二、量化&#xff08;Quantization&#xff09;1、基本概念2、量化的实现8bit量化4bit量化 三、QLora四、大语言模型量化方法对比&#xff1a;GPTQ、GGUF…

勤云远程稿件处理系统 SQL注入漏洞复现(XVE-2024-18393)

0x01 产品简介 勤云远程稿件处理系统(又称勤云采编系统)是北京勤云科技发展有限公司研发的一款产品,是一款全网络版期刊采编管理系统,旨在解决从投稿到稿件发表整个过程的信息化管理问题。该系统集成了搜索引擎、云计算等先进技术,为编辑部、作者和审稿人提供了便捷、高效…

谷歌Gemini与Anthropic Claude对比测试引发争议:AI竞赛暗流涌动

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

vLLM (2) - 架构总览

系列文章目录 vLLM (1) - Qwen2推理&部署 vLLM (2) - 架构总览 vLLM (3) - Sequence & SequenceGroup vLLM (4) - LLMEngine上篇 vLLM (5) - LLMEngine下篇 vLLM (6) - Scheduler & BlockSpaceManager 文章目录 系列文章目录前言一、官方资料二、原理简述三、架构…

QT从入门到精通(三)——实现文件列表遍历的图像浏览器

使用 Qt 实现文件列表遍历的图像浏览器 在本篇博客中将介绍如何使用 Qt 框架创建一个简单的图像浏览器应用程序。该应用程序能够选择文件夹&#xff0c;遍历其中的图像文件&#xff0c;并显示这些图像。我们将重点关注如何使用 Qt 的文件对话框和 OpenCV 库来处理图像。 1. 项…

强化特种作业管理,筑牢安全生产防线

在各类生产经营活动中&#xff0c;特种作业由于其操作的特殊性和高风险性&#xff0c;一直是安全生产管理的重点领域。有效的特种作业管理体系涵盖多个关键方面&#xff0c;从作业人员的资质把控到安全设施的配备维护&#xff0c;再到特种设备的精细管理以及作业流程的严格规范…

数据库管理-第274期 Oracle Enterprise Manager 24ai新特性一览(20241223)

数据库管理274期 2024-12-23 数据库管理-第274期 Oracle Enterprise Manager 24ai新特性一览&#xff08;20241223&#xff09;1 增强的企业管理联邦2 新的导航菜单3 EM仪表盘增强4 使用远程代理进行监控5 0停机监控6 新的作业系统控制台7 Oracle Key Vault支持8 自治数据库的引…

将Minio设置为Django的默认Storage(django-storages)

这里写自定义目录标题 前置说明静态文件收集静态文件 使用django-storages来使Django集成Minio安装依赖settings.py测试收集静态文件测试媒体文件 前置说明 静态文件 Django默认的Storage是本地&#xff0c;项目中的CSS、图片、JS都是静态文件。一般会将静态文件放到一个单独…

嵌入式单片机中蓝牙模块的详解

蓝牙模块的使用 蓝牙模块的特点可以看到蓝牙模块采用的TI公司设计的CC2541芯片,主要面向低功耗蓝牙通信方案,该模块的工作频段为2.4GHz,这个频段属于国际通用频段。 注意:蓝牙集成了一个状态指示灯,LED灯如果均匀慢速闪烁,就表示蓝牙未连接,如果LED灯常亮,表示蓝牙已连接…

互联网视频云平台EasyDSS无人机推流直播技术如何助力野生动植物保护工作?

在当今社会&#xff0c;随着科技的飞速发展&#xff0c;无人机技术已经广泛应用于各个领域&#xff0c;为我们的生活带来了诸多便利。而在动植物保护工作中&#xff0c;无人机的应用更是为这一领域注入了新的活力。EasyDSS&#xff0c;作为一款集视频处理、分发、存储于一体的综…