Мне нужно написать функцию шаблона на 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также есть копирующий вектор, откуда мы/компилятор должны знать, какой параметр вектора извлечь.