C++ 中的静态鸭子类型

Posted

技术标签:

【中文标题】C++ 中的静态鸭子类型【英文标题】:Static duck typing in C++ 【发布时间】:2016-04-27 12:50:07 【问题描述】:

C++ 对模板参数给出的类型有某种鸭子类型。我们不知道DUCK1DUCK2 会是什么类型,但只要他们能quack(),它就会编译并运行:

template <class DUCK1, class DUCK2>
void let_them_quack(DUCK1* donald, DUCK2* daisy)
  donald->quack();
  daisy->quack();

但是写起来有点不方便。当我完全不在乎 DUCK1DUCK2 的实际类型是什么,而是想充分利用鸭子类型的想法时,那么我想要一些与上面略有不同的东西:

    我不想编写一个重复且几乎没有意义的模板参数列表(想象一下如果有 7 只鸭子会发生什么......) 我想更明确一点,从不使用类型,只有接口才是重要的。 我想要某种接口注释/检查。以某种方式明确类型背后的预期接口。 (不过,这与鸭式打字有点不同。)

C++ 是否提供任何功能来实现这 3 个想法中的一个或多个?

(我知道在大多数情况下,虚拟继承是实现此类模式的首选方法,但这里的问题专门针对静态多态的情况。)

【问题讨论】:

concept Duckable... 在您附近的TS 中找到您。 好吧,它还不是标准的,但希望 C++17 允许我们使用 auto 作为函数参数来简化它。 @KerrekSB 它不在 C++17 的案卷上,对吧?也许我们会在 2020 年看到它。:( @erip:您可以询问您的供应商是否实施了 TS。我认为 GCC 和 Clang 会做,或者很快就会做。 @KerrekSB 我的意思是作为标准的一部分。我正在尝试编写越来越多的符合标准的代码。 :) 【参考方案1】:

关于问题 1 和 2:从 C++14 开始,您可以省略显式 template &lt;typename ... 样板并使用 auto,但仅限于 lambda:

auto let_them_quack = [] (auto & donald, auto & daisy)
    donald.quack();
    daisy.quack();
;

(是的,我更喜欢对指针的引用)。 GCC 允许在通常的函数中这样做作为扩展。

对于第 3 题,你所说的就是概念。它们在 C++ 中存在了很长时间,但只是作为一个文档术语。现在 Concepts TS 正在进行中,您可以编写类似的东西

template<typename T>
concept bool Quackable = requires(T a) 
    a.quack();
;

void let_them_quack (Quackable & donald, Quackable & daisy);

请注意,它还不是 C++,只是一个正在进行的技术规范。不过,GCC 6.1 似乎已经支持它。使用当前 C++ 实现概念和约束是可能的;你可以在boost找到一个。

【讨论】:

AFAIK 您的***示例仅适用于 lambdas 而不是免费函数。 @NathanOliver 我也这么认为,但是使用 C++14 的 ideone 和我的本地 gcc 都接受了它。似乎是一个扩展,我会修复答案。 是的,它是一个 gcc 扩展。 我认为这是迄今为止唯一符合问题意图的答案。可惜这些想法还不是标准的...... 始终始终使用 -pedantic 编译您的 gcc 代码 :)【参考方案2】:

我想省略编写重复的模板参数列表 而且大多毫无意义(想象一下如果有 7 个 鸭子...)

为此,您可以使用可变参数模板并执行以下操作:

template<typename DUCK>
void let_them_quack(DUCK &&d) 
  d.quack();


template<typename DUCK, typename... Args>
void let_them_quack(DUCK &&d, Args&& ...args) 
  d.quack();
  let_them_quack(std::forward<Args>(args)...);

Live Demo

【讨论】:

你为什么说d.quack()两次? @KerrekSB 对不起,你能说得清楚一点吗?你的意思是递归的微不足道的步骤吗?【参考方案3】:

#2 和#3 的处理方式是,如果给定的类没有实现接口,代码将无法编译,并引发编译错误。你也可以把它正式化:

class duck 

public:
   virtual void quack()=0;
;

然后将函数的参数声明为带有指向鸭子的指针。您的类必须从此类继承,从而使let_them_quack() 的要求一清二楚。

就 #1 而言,可变参数模板可以解决这个问题。

void let_them_quack()



template <typename ...Args>
void let_them_quack(duck* first_duck, Args && ...args) 
  first_duck->quack();
  let_them_quack(std::forward<Args>(args)...);

【讨论】:

我同意 Kerrek 的观点,而且我还认为该解决方案既没有澄清任何内容,也没有更容易读/写...【参考方案4】:

您将能够通过概念使其看起来更漂亮(尚未成为标准 - 但非常接近):

http://melpon.org/wandbox/permlink/Vjy2U6BPbsTuSK3u

#include <iostream>

template<typename T>concept bool ItQuacks()
    return requires (T a) 
         a.quack()  -> void;
    ;


void let_them_quack2(ItQuacks* donald, ItQuacks* daisy)
  donald->quack();
  daisy->quack();


struct DisneyDuck 
    void quack() std::cout << "Quack!";
;

struct RegularDuck 
    void quack() std::cout << "Quack2!";
;

struct Wolf 
    void woof() std::cout << "Woof!";
;

int main() 
    DisneyDuck q1, q2;
    let_them_quack2(&q1, &q2);

    RegularDuck q3, q4;
    let_them_quack2(&q3, &q4);    

    //Wolf w1, w2;
    //let_them_quack2(&w1, &w2);    // ERROR: constraints not satisfied

输出:

 Quack!Quack!Quack2!Quack2!

如您所见,您将能够:omit writing a template parameter list,ItQuacks 非常明确,因此发生了types are never used and that it's only the interface that matters。这个I'd like to have sort of an interface annotation/check. 也会发生,概念使用也会给你有意义的错误信息。

【讨论】:

自编写以来语法发生了一些变化:主要是删除bool并将auto添加到函数参数中。【参考方案5】:

我们只需要写一个版本的函数:

#include <utility>

template<typename... Quackers>
void let_them_quack(Quackers&& ...quackers) 
  using expand = int[];

  void(expand  0, (std::forward<Quackers>(quackers).quack(), 0)... );


struct Duck 
  void quack() 
;

int main()

  Duck a, b, c;
  let_them_quack(a, b, c, Duck());

【讨论】:

以上是关于C++ 中的静态鸭子类型的主要内容,如果未能解决你的问题,请参考以下文章

python的鸭子类型与多态

C++中的自动存储静态存储和动态存储

C#如何静态调用C++中的方法(静态调用dll)

py基础考察点

c++ 类包含一个与自身类型相同的静态成员。为啥是这种模式?

C++中的动态类型与动态绑定虚函数运行时多态的实现