【问题标题】:Tuple of all complete types satisfying a certain concept满足某个概念的所有完整类型的元组
【发布时间】:2021-07-03 17:06:39
【问题描述】:

我想获得满足某个概念的所有完整类型。这些类型应列为std::tuple 的元素类型。

为:

#include <concepts>
#include <tuple>

class A{};

template<typename T>
concept InterestingType = std::derived_from<T, A>;

class B : public A{};
class C : public A{};
class D{};

我想获得std::tuple&lt;A, B, C&gt; 类型(因为ABC 满足InterestingType 的概念,但D 不满足。)。元组中类的顺序无关紧要(例如std::tuple&lt;C, B, A&gt; 也可以)。重复也可以(例如std::tuple&lt;A, B, C, A, A&gt;)。

这可能吗?

更新:正如 cmets 中所问的,这里计划的目的是回答这个问题:

我有许多预定义的类,它们具有字符串标识符并提供解释字符串的解析构造函数,例如:

struct MyInt {
static constexpr char name[] = "MyInt";
int i_;
MyInt(std::string);
}
struct MyDouble {
static constexpr char name[] = "MyDouble";
double d_;
MyInt(std::string);
}

还有一个工厂方法:

std::any make(std::string type_name, std::string constructor_arg){
// generated code
if (type_name == MyInt::name)
  return MyInt{constructor_arg};
else if (type_name == MyDouble::name)
  return MyDouble{constructor_arg};
else 
  return {};
}

make 实际上调用了一个编译时生成的类似 switch-case 的模板函数,该函数将类型元组(即std::tuple&lt;MyInt, MyDouble&gt;)作为模板参数并生成与上面提供的实现等效的代码。 我目前手动定义注册类型的元组。 这对于库附带的预定义类型是可以的。

我正在努力解决的问题(以及为什么我问第一个问题)是用户如何提供其他类型并注册它们。

最后说明: 由于其他技术原因,虚拟构造函数没有选项。 注册的类型(如MyIntMyDouble)不能有其他虚拟方法技术原因。

更新 2:

我已经使用下面来自 HolyBlackCat 的答案的代码构建了一个工作示例。它在 clang >= 10 下编译得很好,但在所有 gcc 版本上都失败了。结帐https://godbolt.org/z/9xxbo79rr

知道我需要更改什么才能使用 gcc 构建吗?至少 gcc 11 应该能够编译它。

#include <iostream>

#include <any>
#include <string>
#include <map>

using func_ptr = std::any (*)(std::string);
using func_map = std::map<std::string, func_ptr>;

struct Factory {
    inline static func_map &make_funcs() {
        static func_map func_ptrs;
        return func_ptrs;
    }
};

template<typename T>
struct Base {
    inline static const std::nullptr_t dummy = [] {
        // This code will run for each derived class, when the program starts.

        Factory::make_funcs()[T::name] = [](std::string s) -> std::any { return T{std::move(s)}; };
        return nullptr;
    }();

    // Force `dummy` to be instantiated, even though it's unused.
    static constexpr std::integral_constant<decltype(&dummy), &dummy> dummy_helper{};
};

struct MyInt : Base<MyInt> {
    static constexpr char name[] = "MyInt";
    int i_ = 5;

    MyInt(std::string) {}
};

struct MyDouble : Base<MyDouble> {
    static constexpr char name[] = "MyDouble";
    double d_ = 4.0;

    MyDouble(std::string) {}
};


int main() {
    for (auto &[_, make] : Factory::make_funcs()) {
        auto instance = make("a");
        try {
            any_cast<MyDouble>(instance);
            std::cout << "is MyDouble" << std::endl;
        } catch (...) {}

        try {
            any_cast<MyInt>(instance);
            std::cout << "is MyInt" << std::endl;
        } catch (...) {}
    }
}

更新 3

它的工作原理是使dummy 的 init 函数成为一个未在类中定义的单独函数。见https://godbolt.org/z/hnW58zc4s

#include <iostream>

#include <any>
#include <string>
#include <map>

using func_ptr = std::any (*)(std::string);
using func_map = std::map<std::string, func_ptr>;

struct Factory {
    inline static func_map &make_funcs() {
        static func_map func_ptrs;
        return func_ptrs;
    }
};

template<typename T>
struct Base {
    inline static std::nullptr_t init();
    inline static const std::nullptr_t dummy = init();

    // Force `dummy` to be instantiated, even though it's unused.
    static constexpr std::integral_constant<decltype(&dummy), &dummy> dummy_helper{};
};

template<typename T>
std::nullptr_t Base<T>::init() {
    Factory::make_funcs()[T::name] = [](std::string s) -> std::any { return T{std::move(s)}; };
    return nullptr;
}

struct MyInt : Base<MyInt> {
    static constexpr char name[] = "MyInt";
    int i_ = 5;

    MyInt(std::string) {}
};

struct MyDouble : Base<MyDouble> {
    static constexpr char name[] = "MyDouble";
    double d_ = 4.0;

    MyDouble(std::string) {}
};


int main() {
    for (auto &[_, make] : Factory::make_funcs()) {
        auto instance = make("a");
        try {
            any_cast<MyDouble>(instance);
            std::cout << "is MyDouble" << std::endl;
        } catch (...) {}

        try {
            any_cast<MyInt>(instance);
            std::cout << "is MyInt" << std::endl;
        } catch (...) {}
    }
}

【问题讨论】:

  • 不,不可能,至少在添加反射之前是不可能的。如果您告诉我们这是做什么用的,我们或许可以提出其他解决方案...
  • 好的,谢谢。我想等待 Reflectios TS 成为标准的一部分可能需要很长时间。所以我更新了原始问题,详细说明了我需要类型元组的问题。
  • 什么是“虚拟构造函数”?
  • 好问题。我想那是无稽之谈。我想说的是 MyInt 和 MyDouble 不能有虚方法。

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


【解决方案1】:

获得“满足某个概念的所有完整类型” 是不可能的(至少在我们得到反思之前),所以我正在回答问题的第二部分。


第 1 步:您可能希望使用 this 之类的东西来获取类型名称,这样您就不需要在类中使用 name 变量。

第 2 步:创建一个 CRTP 基础,并从中继承您的类:

template <typename T> struct Base {};

struct MyInt : Base<MyInt> {};
struct MyDouble : Base<MyDouble> {};

第三步:将类注册码注入基类:

template <typename T>
class Base
{
    static std::nullptr_t Register()
    {
        // This function will run for each derived class, when the program starts.

        std::ios_base::Init init; // Without this, `std::cout` might not work this early.
        std::cout << "Hello, world!\n";
        return nullptr;
    }

    inline static const std::nullptr_t dummy = Register();

    // Force `dummy` to be instantiated, even though it's unused.
    static constexpr std::integral_constant<decltype(&dummy), &dummy> dummy_helper{};
};

此代码将在您的程序启动时运行,在输入 main 之前,对于派生自 Base&lt;...&gt; 的每个类。

第 4 步:创建一个单例,例如 std::map&lt;std::string, std::any(*)()&gt;,以存储构造每个类的函数。从class Base 中的静态变量初始化器填充它,如上所示。

确保单例是函数中的static 变量,而不是全局变量。否则你会遇到静态初始化命令的失败。

【讨论】:

  • 我才意识到我一直在使用 clang 12 来编译它。使用 gcc 这不会构建。请参阅问题中的更新 2。
  • @A.Bigerl 嗯。找到了解决方法,请参阅编辑。
猜你喜欢
  • 2020-01-27
  • 1970-01-01
  • 2021-07-28
  • 1970-01-01
  • 2023-01-11
  • 1970-01-01
  • 2021-08-29
  • 2021-10-01
相关资源
最近更新 更多