I want to get all complete types that satisfy a certain concept. The types should be listed as element types of a std::tuple
.
For:
#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{};
I would want to get the type std::tuple<A, B, C>
(because A
, B
and C
satisfy the concept InterestingType
but D
doesn't.). The order of the classes in the tuple doesn't matter (e.g. std::tuple<C, B, A>
is also fine). Duplicates would probably also be fine (e.g. std::tuple<A, B, C, A, A>
).
Is this possible?
update: As asked in the comments, here the planned purpose of an answer to this question:
I have a number of predefined classes which have a string identifier and provide a parsing constructor which interprets a string, e.g.:
struct MyInt {
static constexpr char name[] = "MyInt";
int i_;
MyInt(std::string);
}
struct MyDouble {
static constexpr char name[] = "MyDouble";
double d_;
MyInt(std::string);
}
And a factory method:
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
calls actually a compile-time generated switch-case-like template function which takes a tuple of types (i.e. std::tuple<MyInt, MyDouble>
) as template argument and generates code equivalent to the implementation provided above.
I currently define the tuple of registered types manually.
This is OK for predefined types shipped with the library.
What I am struggling with (and why I asked the first question), is how a user can provide additional types and register them.
final note: A virtual constructor is for other technical reasons no option. The registered types (like MyInt
and MyDouble
) cannot have virtual methods for other technical reasons.
update 2:
I've build an working example with the code below from HolyBlackCat's answer below. It compiles fine with clang >= 10 but fails on all gcc versions. Checkout https://godbolt.org/z/9xxbo79rr
Any idea what I need to change so that it builds with gcc? At least gcc 11 should be able to compile it.
#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 (...) {}
}
}
update 3:
It works by making the init function of dummy
a separate function which is not defined in the class. See 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 (...) {}
}
}