满足某个概念的所有完整类型的元组
Tuple of all complete types satisfying a certain concept
我想获取满足某个概念的所有完整类型。类型应列为 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<A, B, C>
(因为 A
、B
和 C
满足概念 InterestingType
但 D
没有。)。元组中 classes 的顺序无关紧要(例如 std::tuple<C, B, A>
也可以)。重复可能也很好(例如 std::tuple<A, B, C, A, A>
)。
这可能吗?
更新: 正如评论中所问,这里是回答这个问题的计划目的:
我有许多预定义的 classes,它们有一个字符串标识符并提供一个解释字符串的解析构造函数,例如:
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<MyInt, MyDouble>
)作为模板参数并生成与上面提供的实现等效的代码.
我目前手动定义注册类型的元组。
这对于库附带的预定义类型是可以的。
我正在努力解决的(以及为什么我问第一个问题)是用户如何提供额外的类型并注册它们。
最后说明: 虚拟构造函数由于其他技术原因没有选择。 已注册的类型(如 MyInt
和MyDouble
) 由于其他技术原因不能有虚拟方法。
更新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 函数成为 class 中未定义的单独函数。参见 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 (...) {}
}
}
获得“满足特定概念的所有完整类型”是不可能的(至少在我们得到反思之前),所以我正在回答问题的第二部分。
第 1 步:您可能想使用 this 之类的东西来获取类型名称,这样您就不需要 classes 中的 name
变量。
第 2 步:制作一个 CRTP 基础,并从中继承您的 classes:
template <typename T> struct Base {};
struct MyInt : Base<MyInt> {};
struct MyDouble : Base<MyDouble> {};
第三步:将class注册码注入基地:
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<...>
.
派生的每个 class
第 4 步:创建一个单例,类似于 std::map<std::string, std::any(*)()>
,以存储构造每个 class 的函数。从 class Base
中的静态变量初始值设定项填充它,如上所示。
确保单例是函数中的一个 static
变量,而不是全局变量。否则,您将遇到静态初始化命令的惨败。
我想获取满足某个概念的所有完整类型。类型应列为 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<A, B, C>
(因为 A
、B
和 C
满足概念 InterestingType
但 D
没有。)。元组中 classes 的顺序无关紧要(例如 std::tuple<C, B, A>
也可以)。重复可能也很好(例如 std::tuple<A, B, C, A, A>
)。
这可能吗?
更新: 正如评论中所问,这里是回答这个问题的计划目的:
我有许多预定义的 classes,它们有一个字符串标识符并提供一个解释字符串的解析构造函数,例如:
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<MyInt, MyDouble>
)作为模板参数并生成与上面提供的实现等效的代码.
我目前手动定义注册类型的元组。
这对于库附带的预定义类型是可以的。
我正在努力解决的(以及为什么我问第一个问题)是用户如何提供额外的类型并注册它们。
最后说明: 虚拟构造函数由于其他技术原因没有选择。 已注册的类型(如 MyInt
和MyDouble
) 由于其他技术原因不能有虚拟方法。
更新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 函数成为 class 中未定义的单独函数。参见 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 (...) {}
}
}
获得“满足特定概念的所有完整类型”是不可能的(至少在我们得到反思之前),所以我正在回答问题的第二部分。
第 1 步:您可能想使用 this 之类的东西来获取类型名称,这样您就不需要 classes 中的 name
变量。
第 2 步:制作一个 CRTP 基础,并从中继承您的 classes:
template <typename T> struct Base {};
struct MyInt : Base<MyInt> {};
struct MyDouble : Base<MyDouble> {};
第三步:将class注册码注入基地:
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<...>
.
第 4 步:创建一个单例,类似于 std::map<std::string, std::any(*)()>
,以存储构造每个 class 的函数。从 class Base
中的静态变量初始值设定项填充它,如上所示。
确保单例是函数中的一个 static
变量,而不是全局变量。否则,您将遇到静态初始化命令的惨败。