Вывести типы аргументов конструктора для шаблона класса на C++?

Мне нужно написать функцию шаблона на 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);
}

Что произойдет, если существует более одного актера? В вашем примере Test также есть копирующий вектор, откуда мы/компилятор должны знать, какой параметр вектора извлечь.

user12002570 03.09.2024 09:49

Что может сработать, так это то, что boost.pfr делает для определения типов членов структуры (создайте класс с шаблонным operator T, попробуйте передать его конструктору, используйте метапрограммирование с сохранением состояния для извлечения типа, в который он был преобразован). Но это может оказаться довольно сложным.

HolyBlackCat 03.09.2024 09:50

Вопрос также неясен. Что касается вашего класса, то есть два вектора Test. Как мы можем определить тип аргумента всего лишь по get_tuple<Test>

user12002570 03.09.2024 09:52

Ваша цель — узнать возможные типы аргументов или узнать, можно ли сконструировать данный тип из аргументов некоторых типов?

Oersted 03.09.2024 09:55

«Мне нужно написать на C++ функцию-шаблон, которая извлекает типы конструктора класса…» Почему? Какова основная проблема, которую вы пытаетесь решить?

user12002570 03.09.2024 10:00

Редактирование по-прежнему не разъясняет, что должно произойти, если имеется более одного актера.

user12002570 03.09.2024 10:24

@user12002570 user12002570 Рассмотрим только случай, когда есть один актер.

Jcfly Lv 03.09.2024 10:27

@JcflyLv В вашем примере Test есть 3 конструктора

Caleth 03.09.2024 10:34

@JcflyLv Ваш класс Test на самом деле имеет несколько конструкторов. Обратите внимание, что C++ — один из самых трудных/сложных языков для изучения. Хорошая книга по C++ должна помочь понять, почему Test имеет несколько векторов.

user12002570 03.09.2024 10:36

Вы не можете вызвать конструктор. Вместо этого, если это действительно необходимо, вы можете добавить кортеж к каждому классу и заполнить этот кортеж нужными типами. Затем вы можете передать тип класса в качестве параметра шаблона и получить доступ к кортежу, например class_name::ctor_args_tuple

NathanOliver 03.09.2024 15:37
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
113
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Однако этот подход не работает для классов без конструкторов по умолчанию. Проблема, с которой вы столкнулись, связана с тем, что конструкторы — это специальные функции-члены, которые нельзя получить по адресу или использовать напрямую в качестве указателей на функции. Однако вы можете использовать методы 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++.

Jcfly Lv 03.09.2024 17:03
Ответ принят как подходящий

Спасибо @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.

Другие вопросы по теме