首页 > 解决方案 > 如何创建共享概念的对象向量?

问题描述

我想创建一个包含不同类型但都共享相同概念的对象的向量(或数组)。
类似于Vec<Box<dyn trait>>Rust。

struct Dog {
    void talk() {
        std::cout << "guau guau" << std::endl;
    }
};

struct Cat {
    void talk() {
        std::cout << "miau miau" << std::endl;
    }
};

template <typename T>
concept Talk = requires(T a) {
    { a.talk() } -> std::convertible_to<void>;
};

int main() {
    auto x = Dog{};
    auto y = Cat{};

    ??? pets = {x, y};

    for(auto& pet: pets) {
        pet.talk();
    }

    return 0;
}

标签: c++c++20c++-concepts

解决方案


您要查找的内容通常称为“类型擦除”。C++20concept的语言特性不(也不能)支持类型擦除——该特性完全限于约束模板(类模板、函数模板、类模板的成员函数等),不能真正用于任何其他语境。

您必须改为Talk手动编写可擦除的类型,或者求助于使用可用的类型擦除库之一。

例如,使用dyno(Louis Dionne 在CppCon 2017CppNow 2018上进行了几次演讲),看起来如下。你会注意到我使用这个概念的唯一地方Talk是约束默认的概念图:

#include <dyno.hpp>
#include <vector>
#include <iostream>
using namespace dyno::literals;

// this is the "concept" we're going to type erase
struct PolyTalkable : decltype(dyno::requires_(
    dyno::CopyConstructible{},
    dyno::Destructible{},
    "talk"_s = dyno::method<void()>
)) { };

template <typename T>
concept Talk = requires (T a) { a.talk(); };

// this how we implement our "concept"
template <Talk T>
auto const dyno::default_concept_map<PolyTalkable, T> = dyno::make_concept_map(
    "talk"_s = [](T& self) { self.talk(); }
);

// this is our hand-written "dyn PolyTalkable"
class DynTalkable {
    dyno::poly<PolyTalkable> impl_;
public:
    template <typename T>
        requires (!std::same_as<T, DynTalkable>
               && dyno::models<PolyTalkable, T>())
    DynTalkable(T t) : impl_(t) { }

    void talk() {
        impl_.virtual_("talk"_s)();
    }
};

struct Dog {
    void talk() {
        std::cout << "guau guau" << std::endl;
    }
};

struct Cat {
    void talk() {
        std::cout << "miau miau" << std::endl;
    }
};

int main() {
    std::vector<DynTalkable> pets;
    pets.push_back(Dog{});
    pets.push_back(Cat{});
    for (auto& pet : pets) {
        pet.talk();
    }
}

有关 C++ 类型擦除的其他资源,另请参阅:

  • Sean Parent 的Inheritance 是 Base Class of EvilC++ Seasoning的演讲,无论如何每个人都应该看到。这演示了一种执行运行时多态性的机制。
  • Sy Brand 的带有元类的动态多态性(他们在多个会议上发表了这个演讲,最近一次是ACCU 2021)。这演示了如何使用反射工具编写类型擦除库,从而使整体代码比我上面展示的要少得多。

推荐阅读