C++中的静态鸭子键入

Static duck typing in C++

本文关键字:鸭子 静态 C++      更新时间:2023-10-16

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

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

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

  1. 我想省略编写重复且几乎毫无意义的模板参数列表(想象一下如果有 7 只鸭子会发生什么......
  2. 我想更明确一点,这些类型永远不会被使用,只有接口才是重要的。
  3. 我想要一个界面注释/检查。以某种方式明确类型背后的接口。(然而,这与鸭子打字有点对比。

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

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

关于问题 1 和 2:从 C++14 开始,您可以省略显式template <typename ...样板并使用 auto ,但只能在 lambda 中:

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

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

对于问题3,您所说的称为概念。它们在C++中存在了很长时间,但仅作为文档术语。现在概念 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中找到一个。

我想省略编写重复的模板参数列表 而且大多毫无意义(想象一下如果有 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)...);
}

现场演示

#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)...);
}

您将能够使它看起来更具有概念(尚未达到标准 - 但非常接近):

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.也会发生,概念的使用也会给你有意义的错误消息。

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

#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());
}