Индексирование кортежа во время выполнения - PullRequest
1 голос
/ 31 января 2020

Предположим, у меня есть переменная constructors, которая является кортежем функций-конструкторов, представленных в виде переменных c generi c lambdas.

// types for constructors 
using type_tuple = std::tuple<ClassA, ClassB, ClassC>;

// Get a tuple of constructors(variadic generic lambda) of types in type_tuple
auto constructors = execute_all_t<type_tuple>(get_construct());

// For definitions of execute_all_t and get_construct, see link at the bottom.

Я могу создать экземпляр объекта с помощью:

// Create an object using the constructors, where 0 is index of ClassA in the tuple.
ClassA a = std::get<0>(constructors)(/*arguments for any constructor of ClassA*/);

Можно ли индексировать тип во время выполнения с помощью magic_get, как показано ниже?

auto obj = magic_get(constructors, 0)(/*arguments for any constructor of ClassA*/);

// Maybe obj can be a std::variant<ClassA, ClassB, ClassC>, which contains object of ClassA?

Редактировать : В идеале obj должен быть экземпляром ClassA. Если это невозможно, я могу принять obj как std::variant<ClassA, ClassB, ClassC>.

Пожалуйста, посмотрите минимальный воспроизводимый пример: Попробуйте онлайн!


A похожий вопрос: C ++ 11 способ индексировать кортеж во время выполнения без использования ключа .

Ответы [ 2 ]

2 голосов
/ 31 января 2020

Вы можете получить время выполнения get return std::variant, что-то вроде:

template <typename ... Ts, std::size_t ... Is>
std::variant<Ts...> get_impl(std::size_t index,
                             std::index_sequence<Is...>,
                             const std::tuple<Ts...>& t)
{
    using getter_type = std::variant<Ts...> (*)(const std::tuple<Ts...>&);
    getter_type funcs[] = {+[](const std::tuple<Ts...>& tuple)
                            -> std::variant<Ts...>
                            { return std::get<Is>(tuple); } ...};

    return funcs[index](t);
}

template <typename ... Ts>
std::variant<Ts...> get(std::size_t index, const std::tuple<Ts...>& t)
{
    return get_impl(index, std::index_sequence_for<Ts...>(), t);
}

Тогда вы можете std::visit ваш вариант сделать то, что вы хотите.

Демо

или для вашего "заводского" примера:

int argA1 = /*..*/;
std::string argA2 = /*..*/;
int argB1 = /*..*/;
// ...

auto obj = std::visit(overloaded{
                [&](const A&) -> std::variant<A, B, C> { return A(argA1, argA2); },
                [&](const B&) -> std::variant<A, B, C> { return B(argB1); },
                [&](const C&) -> std::variant<A, B, C> { return C(); },
            }, get(i, t))
1 голос
/ 05 февраля 2020

Вероятно, это можно сделать более красиво, но в комментариях приведена попытка в соответствии с вашими требованиями.

Требуется C ++ 17, работает на Clang, но выдает Внутренняя ошибка компилятора на G CC.

Однако требуется, чтобы вы сделали конструкционную функцию дружественной к SFINAE, иначе нет способа проверить, может ли она быть вызвана:

Так что используйте

return [](auto... args) -> decltype(U(args)...) { return U(args...); };

вместо

return [](auto... args) { return U(args...); };

Поведение этой функции с учетом аргументов tup и index выглядит следующим образом:

Возвращает лямбда, которая при вызове со списком аргументов возвращает std::variant всех типов, которые могут возникнуть в результате вызовов вида std::get<i>(tup)(/*arguments*/). Какой из них действительно вызывается и сохраняется в возвращенном варианте, определяется во время выполнения с помощью аргумента index. Если index относится к элементу кортежа, который не может быть вызван как бы std::get<index>(tup)(/*arguments*/), то во время выполнения выдается исключение.

Промежуточная лямбда может быть сохранена и вызвана позже. Тем не менее, обратите внимание, что он сохраняет ссылку на аргумент tup, поэтому вам нужно убедиться, что аргумент перестаёт быть лямбда-выражением, если вы не вызываете его и не сбрасываете его немедленно. ++ 20, вы можете упростить это,

  • , используя std::remove_cvref_t<Tup> вместо std::remove_const_t<std::remove_reference_t<Tup>>

  • , изменив определение unwrap на :

    template<auto A>
    using unwrap = typename decltype(A)::type;
    

    и использование его как unwrap<...> вместо unwrap<+...>, что также позволяет удалить operator+ из wrap_t.


Цель wrap / unwrap:

wrap_t - превратить тип в значение, которое я могу передавать в функции и возвращать из них, не создавая объект исходного типа (который может вызвать все виды проблем). На самом деле это просто пустая структура, настроенная на тип и псевдоним типа type, который возвращает тип.

Я написал wrap как глобальную встроенную переменную, так что я могу написать wrap<int> вместо из wrap<int>{}, так как я считаю дополнительные скобки раздражающими.

unwrap<...> не на самом деле не требуется. typename decltype(...)::type делает то же самое, он просто возвращает тип, который представляет экземпляр wrap.

Но опять же я хотел найти более простой способ его написания, но без C ++ 20 это на самом деле невозможно в хороший способ В C ++ 20 я могу просто передать объект wrap непосредственно в качестве аргумента шаблона, но это не работает в C ++ 17.

Так что в C ++ 17 я «разлагаю» объект на указатель, который может быть аргументом шаблона не-типа, с перегруженным operator+, имитирующим синтаксис обычного трюка с лямбда-указателем на функцию, используя унарный оператор + (но я мог бы использовать любой другой унарный оператор).

Фактическое значение указателя не имеет значения, мне нужен только тип, но аргумент шаблона должен быть константным выражением, поэтому я позволю ему быть нулевым указателем. Последнее требование заключается в том, что я не использую встроенный оператор адреса & вместо перегруженного +.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...