std :: tuple_cat, но только с уникальными элементами - PullRequest
10 голосов
/ 14 июня 2019

Мне нужна функция constexpr, которая очень похожа на std::tuple_cat, но вместо того, чтобы объединить все элементы независимо от того, что они в один кортеж, мне нужно добавить данный элемент, только если этот тип имеетНе было добавлено.

Было бы неплохо передать предикат в std::tuple_cat, но такого API не существует (к моему большому сожалению).Я видел несколько способов найти объединенный тип, используя черты типа, которые я не совсем понял, но ничего в форме функции constexpr.Я не уверен, как все это собрать, хотя я уверен, что это можно сделать.

Примерно так:

std::tuple<int, short, char> First;
std::tuple<short, float> Second;
std::tuple<int, double, short> Third;

std::tuple<int, short, char, float, double> Result = tuple_cat_unique(First,Second,Third);

1 Ответ

11 голосов
/ 14 июня 2019

Возможное решение:

template<std::size_t i, class Tuple, std::size_t... is>
constexpr auto element_as_tuple(const Tuple& tuple, std::index_sequence<is...>)
{
    if constexpr (!(std::is_same_v<std::tuple_element_t<i, Tuple>, 
                  std::tuple_element_t<is, Tuple>> || ...))
        return std::make_tuple(std::get<i>(tuple));
    else
        return std::make_tuple();
}

template<class Tuple, std::size_t... is>
constexpr auto make_tuple_unique(const Tuple& tuple, std::index_sequence<is...>)
{
    return std::tuple_cat(element_as_tuple<is>(tuple, 
                          std::make_index_sequence<is>{})...);
}

template<class... Tuples>
constexpr auto make_tuple_unique(const Tuples&... tuples)
{
    auto all = std::tuple_cat(tuples...);
    constexpr auto size = std::tuple_size_v<decltype(all)>;
    return make_tuple_unique(all, std::make_index_sequence<size>{});
}

constexpr std::tuple<int, short, char> first(1, 2, 3);
constexpr std::tuple<short, float> second(4, 5);
constexpr std::tuple<int, double, short> third(6, 7, 8);
constexpr auto t = make_tuple_unique(first, second, third);
static_assert(std::get<0>(t) == 1);
static_assert(std::get<1>(t) == 2);
static_assert(std::get<2>(t) == 3);
static_assert(std::get<3>(t) == 5);
static_assert(std::get<4>(t) == 7);

Обобщение, которое будет работать и с подвижными типами:

template<std::size_t i, class Tuple, std::size_t... is>
constexpr auto element_as_tuple(Tuple&& tuple, std::index_sequence<is...>)
{
    using T = std::remove_reference_t<Tuple>;
    if constexpr (!(std::is_same_v<std::tuple_element_t<i, T>, 
                  std::tuple_element_t<is, T>> || ...))         
        // see below
        // return std::forward_as_tuple(std::get<i>(std::forward<Tuple>(tuple)));
        return std::tuple<std::tuple_element_t<i, T>>(
            std::get<i>(std::forward<Tuple>(tuple)));
    else
        return std::make_tuple();
}

template<class Tuple, std::size_t... is>
constexpr auto make_tuple_unique(Tuple&& tuple, std::index_sequence<is...>)
{
    return std::tuple_cat(element_as_tuple<is>(std::forward<Tuple>(tuple), 
        std::make_index_sequence<is>())...);
}

template<class... Tuples>
constexpr auto make_tuple_unique(Tuples&&... tuples)
{
    auto all = std::tuple_cat(std::forward<Tuples>(tuples)...);
    return make_tuple_unique(std::move(all),
        std::make_index_sequence<std::tuple_size_v<decltype(all)>>{});
}

Добавление / исправление .

Мое первоначальное тестирование показало, что оно работает нормально, но более глубокие тесты показали, что использование std::forward_as_tuple создает ссылки на временные переменные (переменная "all" в make_tuple_unique).Мне пришлось изменить это std::forward_as_tuple на std::make_tuple, и все было исправлено.

Это правильно: если вы передаете значение r в качестве аргумента, как

make_tuple_unique(std::tuple<int>(1))

тип возвращаемого значения std::tuple<int&&>, и вы получите висячую ссылку.Но с std::make_tuple вместо std::forward_as_tuple

make_tuple_unique(std::tuple<int&>(i))

будет иметь тип std::tuple<int>, и ссылка будет потеряна.С std::make_tuple мы теряем lvalues, с std::forward_as_tuple мы теряем простые значения.Чтобы сохранить первоначальный тип, мы должны

return std::tuple<std::tuple_element_t<i, T>>(
    std::get<i>(std::forward<Tuple>(tuple)));
...