Мне нужно написать функцию шаблона на C++, которая извлекает типы аргументов конструктора класса и возвращает их в виде std::tuple. Вот чего я пытаюсь достичь:
#include <tuple>
#include <type_traits>
template <class T>
struct get_tuple
{
// return the types of T's constructor arguments as a tuple
// using type = ...
};
struct Test
{
Test(int, double) {}
};
static_assert(std::is_same_v<typename get_tuple<Test>::type, std::tuple<int, double>>);
Я попробовал следующий код, но он не удался, поскольку мы не можем получить указатель функции на конструктор.
template <class T, class... Args>
std::tuple<Args...> get_tuple_impl(T (*)(Args...));
template <class T>
struct get_tuple
{
using type = decltype(get_tuple_impl(&T::T));
};
Чего я хочу добиться, так это фабрики, которая создает экземпляры T на основе параметров, указанных в конфигурации. нравиться
template<class T>
T construct()
{
typename get_tuple<T>::type args;
// fill args with config
return std::make_from_tuple<T>(args);
}
Что может сработать, так это то, что boost.pfr делает для определения типов членов структуры (создайте класс с шаблонным operator T
, попробуйте передать его конструктору, используйте метапрограммирование с сохранением состояния для извлечения типа, в который он был преобразован). Но это может оказаться довольно сложным.
Вопрос также неясен. Что касается вашего класса, то есть два вектора Test
. Как мы можем определить тип аргумента всего лишь по get_tuple<Test>
Ваша цель — узнать возможные типы аргументов или узнать, можно ли сконструировать данный тип из аргументов некоторых типов?
«Мне нужно написать на C++ функцию-шаблон, которая извлекает типы конструктора класса…» Почему? Какова основная проблема, которую вы пытаетесь решить?
Редактирование по-прежнему не разъясняет, что должно произойти, если имеется более одного актера.
@user12002570 user12002570 Рассмотрим только случай, когда есть один актер.
@JcflyLv В вашем примере Test
есть 3 конструктора
@JcflyLv Ваш класс Test
на самом деле имеет несколько конструкторов. Обратите внимание, что C++ — один из самых трудных/сложных языков для изучения. Хорошая книга по C++ должна помочь понять, почему Test
имеет несколько векторов.
Вы не можете вызвать конструктор. Вместо этого, если это действительно необходимо, вы можете добавить кортеж к каждому классу и заполнить этот кортеж нужными типами. Затем вы можете передать тип класса в качестве параметра шаблона и получить доступ к кортежу, например class_name::ctor_args_tuple
Однако этот подход не работает для классов без конструкторов по умолчанию. Проблема, с которой вы столкнулись, связана с тем, что конструкторы — это специальные функции-члены, которые нельзя получить по адресу или использовать напрямую в качестве указателей на функции. Однако вы можете использовать методы SFINAE (ошибка замены не является ошибкой), чтобы определить типы аргументов конструктора.
Вот обновленная реализация:
#include <tuple>
#include <utility>
// Helper struct to extract constructor argument types
template <typename T, typename = void>
struct get_constructor_args;
// Specialization for non-aggregate initialization
template <typename T>
struct get_constructor_args<T, std::void_t<decltype(T{std::declval<std::add_pointer_t<T>>()})>>
{
static constexpr auto size = 0;
using type = std::tuple<>;
};
// Specialization for aggregate initialization
template <typename T>
struct get_constructor_args<T, std::enable_if_t<(sizeof...(std::tuple_element_t<0, T>) > 1)>>
{
static constexpr auto size = sizeof...(std::tuple_element_t<0, T>);
using type = std::tuple<std::remove_reference_t<decltype(std::get<0>(std::declval<T>()))>...>;
};
// Specialization for single-argument constructors
template <typename T, typename Arg>
struct get_constructor_args<T, std::void_t<decltype(T{std::declval<Arg>()})>>
{
static constexpr auto size = 1;
using type = std::tuple<Arg>;
};
// General case: try to find a matching constructor
template <typename T, typename... Args>
struct get_constructor_args<T, std::void_t<decltype(T{std::declval<Args>()...})>>
{
static constexpr auto size = sizeof...(Args);
using type = std::tuple<Args...>;
};
Теперь ваша структура get_tuple
становится простой:
template <class T>
struct get_tuple : public get_constructor_args<T> {};
И вот как вы можете определить свою функцию construct
:
template <class T>
T construct() {
typename get_tuple<T>::type args;
// Fill args from configuration...
return std::apply([](auto&&... args){ return T(args...); }, args);
}
В этом решении используется SFINAE для определения того, какая специализация соответствует данному типу T
. Первая специализация обрабатывает случаи, когда конструктор не найден (например, когда T
имеет только удаленные конструкторы). Вторая специализация охватывает агрегатную инициализацию. Третий касается конструкторов с одним аргументом, а последний пытается сопоставить конструкторы с несколькими аргументами.
Обратите внимание, что это решение предполагает, что все конструкторы имеют отдельные списки параметров; если есть перегруженные конструкторы с разным количеством аргументов, это будет работать неправильно. Кроме того, имейте в виду, что это не очень хорошо работает с типами, предназначенными только для перемещения, поскольку во время построения им требуются ссылки lvalue на свои аргументы.
Пример использования:
int main() {
struct MyType { MyType(int, int) {} };
static_assert(std::is_same_v<typename get_tuple<MyType>::type, std::tuple<int, int>>);
struct AnotherType { AnotherType(double) {} };
static_assert(std::is_same_v<typename get_tuple<AnotherType>::type, std::tuple<double>>);
struct AggregateType { int x; double y; };
static_assert(std::is_same_v<typename get_tuple<AggregateType>::type, std::tuple<int, double>>);
return 0;
}
Кажется, код не может скомпилироваться под g++.
Спасибо @HolyBlackCat. Изучив boost.fpr, я наконец разобрался
#include <tuple>
#include <array>
struct ubiq_constructor
{
std::size_t ignore;
template <class T>
constexpr operator T() const { return T{}; }
};
template <class T, std::size_t I0, std::size_t... I>
constexpr auto arg_count(std::size_t &out, std::index_sequence<I0, I...>)
-> typename std::add_pointer<decltype(T{ubiq_constructor{I0}, ubiq_constructor{I}...})>::type
{
out = sizeof...(I) + 1;
return nullptr;
}
template <class T, std::size_t... I>
constexpr void arg_count(std::size_t &out, std::index_sequence<I...>)
{
arg_count<T>(out, std::make_index_sequence<sizeof...(I) - 1>{});
}
template <class T>
constexpr std::size_t arg_count()
{
std::size_t count{};
arg_count<T>(count, std::make_index_sequence<10>());
return count;
}
//
template <class T>
struct identity
{
typedef T type;
};
template <std::size_t Index>
using size_t_ = std::integral_constant<std::size_t, Index>;
#define REGISTER_TYPE(Type, Index) \
constexpr std::size_t type_to_id(identity<Type>) \
{ \
return Index; \
} \
constexpr Type id_to_type(size_t_<Index>) \
{ \
return Type{}; \
}
REGISTER_TYPE(double, 1)
REGISTER_TYPE(int, 2)
template <std::size_t I>
struct ubiq_val
{
std::size_t *ref_;
template <class Type>
constexpr operator Type() const noexcept
{
ref_[I] = type_to_id(identity<Type>{});
return Type{};
}
};
template <class T, std::size_t... I>
constexpr auto get_type_ids(std::index_sequence<I...>)
{
std::array<std::size_t, sizeof...(I)> types{};
T tmp{ubiq_val<I>{types.data()}...};
(void)tmp;
return types;
}
template <class T, std::size_t... I>
constexpr auto get_tuple_impl(std::index_sequence<I...> seq)
{
constexpr auto types = get_type_ids<T>(seq);
return std::tuple<decltype(id_to_type(size_t_<types[I]>{}))...>{};
}
template <class T>
constexpr auto get_tuple()
{
constexpr std::size_t count = arg_count<T>();
return get_tuple_impl<T>(std::make_index_sequence<count>{});
}
struct Test
{
constexpr Test(int, double) {}
};
int main()
{
static_assert(std::is_same_v<decltype(get_tuple<Test>()), std::tuple<int, double>>);
return 0;
}
Однако для этого требуется, чтобы конструктор Test был constexpr.
Что произойдет, если существует более одного актера? В вашем примере
Test
также есть копирующий вектор, откуда мы/компилятор должны знать, какой параметр вектора извлечь.