Создать новый std::tuple из подмножества имеющегося
От: SaZ  
Дата: 21.02.23 18:49
Оценка:
Всем добра,

Ломаю голову над следующей задачей. Дан некий тупл, нужно получить новый, в котором будут только определённые типы (например, унаследованные от некоего my_base класса).
Как это сделать?
Тупл отдаётся сторонней библиотекой. Хочу написать враппер, который возвращает новый. К примеру, из исходного тупла нужно получить новый в котором будут только строки и целые числа:
std::tuple<int, string, bool, string, float> -> как получить? -> std::tuple<int, string, string>;

Типы заранее я не знаю, их будут определять пользователи фреймворка.

Контекст следующий, но не думаю что это важно: использую sqlite_orm, нужно автоматизировать создание некоторых таблиц на основе имеющихся, но с модификациями. В этом фреймворке типы нужно указать во время компиляции.
In-memory sqlite используется в качестве внутренней структуры данных при работе gui приложения. Хочу прикрутить автоматическое ведение истории изменений и автоматизировать создание undo/redo команд.
Отредактировано 21.02.2023 19:11 SaZ . Предыдущая версия .
Re: Создать новый std::tuple из подмножества имеющегося
От: Voivoid Россия  
Дата: 21.02.23 21:04
Оценка: 7 (2)
Здравствуйте, SaZ, Вы писали:

SaZ>Всем добра,


SaZ>Ломаю голову над следующей задачей. Дан некий тупл, нужно получить новый, в котором будут только определённые типы (например, унаследованные от некоего my_base класса).

SaZ>Как это сделать?
Если есть C++17, то можно как-то так — https://godbolt.org/z/E6K8Pda36
#include <tuple>
#include <string>
#include <iostream>

auto typeToFilter(int i) { return std::make_tuple(i); }
auto typeToFilter(const std::string& s) { return std::make_tuple(s); }

template <typename T>
std::tuple<> typeToFilter(const T&) { return {}; }

int main() {

    auto tuple = std::make_tuple(123, std::string("hello"), true, std::string("world"), 0.123);
    auto filtered = std::apply([](const auto&...ts) {
        return std::tuple_cat(typeToFilter(ts)...);
    }, tuple);

    std::cout << std::get<0>(filtered) << std::endl;
    std::cout << std::get<1>(filtered) << std::endl;
    std::cout << std::get<2>(filtered) << std::endl;

    return 0;
}


В принципе можно и на C++11 написать с помощью рекурсии, но конечно кода получится побольше
Re[2]: Создать новый std::tuple из подмножества имеющегося
От: SaZ  
Дата: 21.02.23 21:08
Оценка:
Здравствуйте, Voivoid, Вы писали:

V>Здравствуйте, SaZ, Вы писали:


SaZ>>Всем добра,


SaZ>>Ломаю голову над следующей задачей. Дан некий тупл, нужно получить новый, в котором будут только определённые типы (например, унаследованные от некоего my_base класса).

SaZ>>Как это сделать?
V>Если есть C++17, то можно как-то так — https://godbolt.org/z/E6K8Pda36
V>
V>#include <tuple>
V>#include <string>
V>#include <iostream>

V>auto typeToFilter(int i) { return std::make_tuple(i); }
V>auto typeToFilter(const std::string& s) { return std::make_tuple(s); }

V>template <typename T>
V>std::tuple<> typeToFilter(const T&) { return {}; }

V>int main() {

V>    auto tuple = std::make_tuple(123, std::string("hello"), true, std::string("world"), 0.123);
V>    auto filtered = std::apply([](const auto&...ts) {
V>        return std::tuple_cat(typeToFilter(ts)...);
V>    }, tuple);

V>    std::cout << std::get<0>(filtered) << std::endl;
V>    std::cout << std::get<1>(filtered) << std::endl;
V>    std::cout << std::get<2>(filtered) << std::endl;

V>    return 0;
V>}
V>


V>В принципе можно и на C++11 написать с помощью рекурсии, но конечно кода получится побольше


Спасибо за направление, сейчас попробую. У нас С++20 (частично).
Re[2]: Создать новый std::tuple из подмножества имеющегося
От: SaZ  
Дата: 21.02.23 22:18
Оценка:
Здравствуйте, Voivoid, Вы писали:

V>Здравствуйте, SaZ, Вы писали:


SaZ>>Всем добра,


SaZ>>Ломаю голову над следующей задачей. Дан некий тупл, нужно получить новый, в котором будут только определённые типы (например, унаследованные от некоего my_base класса).

SaZ>>Как это сделать?
V>Если есть C++17, то можно как-то так — https://godbolt.org/z/E6K8Pda36
V>
V>...
V>


V>В принципе можно и на C++11 написать с помощью рекурсии, но конечно кода получится побольше


Спасибо, то что надо, но я никак не могу осилить enable_if:

// template <typename T, std::enable_if_t<std::is_convertible_v<T*, my_base *>, bool> = true>
// template <typename T, typename = std::enable_if_t<std::is_base_of_v<my_base, T>, bool>>
template <typename T, std::enable_if_t<std::is_base_of_v<my_base, T>, bool> = true>
auto my_filter(const T& v)
{
    return std::make_tuple(v);
}


Эта перегрузка никогда не вызывается. Хотя пробовал static_assert вне этой функции — всё ок.
Re[3]: Создать новый std::tuple из подмножества имеющегося
От: Voivoid Россия  
Дата: 21.02.23 22:57
Оценка: 2 (1)
Здравствуйте, SaZ, Вы писали:

SaZ>Спасибо, то что надо, но я никак не могу осилить enable_if:

А другую шаблонную перегрузку отключил с соответствующим условием? См. https://godbolt.org/z/ec9rPWcEW
Re: Создать новый std::tuple из подмножества имеющегося
От: rg45 СССР  
Дата: 22.02.23 02:16
Оценка: 6 (1) +1
Здравствуйте, SaZ, Вы писали:

SaZ>Всем добра,

SaZ>Ломаю голову над следующей задачей. Дан некий тупл, нужно получить новый, в котором будут только определённые типы (например, унаследованные от некоего my_base класса).
SaZ>Как это сделать?
SaZ>Тупл отдаётся сторонней библиотекой. Хочу написать враппер, который возвращает новый. К примеру, из исходного тупла нужно получить новый в котором будут только строки и целые числа:
SaZ>
SaZ>std::tuple<int, string, bool, string, float> -> как получить? -> std::tuple<int, string, string>;
SaZ>

SaZ>Типы заранее я не знаю, их будут определять пользователи фреймворка.

Ну, как-то так. Для теcта заюзал fold expressions, поэтому C++17. Но принципиально не вижу преград для понижения до C++14

Идея такая: сначала, основываясь на типе входного тупла и списке типов-фильтров, выводим список индексов элементов входного тупла, которые попадают в конечный тупл после фильтрации (метафункция make_filtered_index_sequence), а также тип конечного тупла (filtered_tuple_t) — все это в компайл-тайме, разумеется. После чего одним простым выражением конструируем конечный отфильтрованный тупл (make_filtered_tuple).

Имплементация, конечно, несколько громоздкая, зато использование — проще некуда, по-моему.

   const auto tuple = std::make_tuple("Morning", 42, std::string("Hello"), 3.14, true, std::string("World"), 'A');

   print(make_filtered_tuple<int, std::string>(tuple)); // -> 42, Hello, World, 
   print(make_filtered_tuple<double, const char*, char>(tuple)); // -> Morning, 3.14, A,


http://coliru.stacked-crooked.com/a/14cec1107571c9a1

  Полный текст примера
#include <iostream>
#include <string>
#include <tuple>
#include <utility>

//////////////////////////////////////////////
// Определяем, находится ли тип в списке типов

template <typename T, typename...>
struct is_in_the_list : std::false_type {};

template <typename T, typename U, typename...X>
struct is_in_the_list<T, U, X...> : is_in_the_list<T, X...> {};

template <typename T, typename...X>
struct is_in_the_list<T, T, X...> : std::true_type {};

//////////////////////////////////////////////////////////////////////////////
// Строим список индексов входного тупла, которые попадают в конечный тупл

template <typename Tuple, typename FilterTuple, typename I = std::index_sequence<0>, typename = void>
struct filtered_index_sequence;

template <typename T, typename...S, typename...F, size_t I, size_t...J>
struct filtered_index_sequence<std::tuple<T, S...>, std::tuple<F...>, std::index_sequence<I, J...>,
   std::enable_if_t<is_in_the_list<T, F...>::value>> :
   filtered_index_sequence<std::tuple<S...>, std::tuple<F...>, std::index_sequence<I + 1, J..., I>> { };

template <typename T, typename...S, typename...F, size_t I, size_t...J>
struct filtered_index_sequence<std::tuple<T, S...>, std::tuple<F...>, std::index_sequence<I, J...>,
   std::enable_if_t<!is_in_the_list<T, F...>::value>> :
   filtered_index_sequence<std::tuple<S...>, std::tuple<F...>, std::index_sequence<I + 1, J...>> { };

template <typename...F, size_t I, size_t...J>
struct filtered_index_sequence<std::tuple<>, std::tuple<F...>, std::index_sequence<I, J...>>
{
   using type = std::index_sequence<J...>;
};

template <typename Tuple, typename...Filter>
using make_filtered_index_sequence = typename filtered_index_sequence<Tuple, std::tuple<Filter...>>::type;

///////////////////////////////
// Выводим тип конечного тупла

template <typename Tuple, typename...Filter>
struct filtered_tuple;

template <typename...T, size_t...I>
struct filtered_tuple<std::tuple<T...>, std::index_sequence<I...>> {
   using type = std::tuple<std::tuple_element_t<I, std::tuple<T...>>...>;
};

template <typename...T, typename...Filter>
struct filtered_tuple<std::tuple<T...>, Filter...> :
filtered_tuple<std::tuple<T...>, make_filtered_index_sequence<std::tuple<T...>, Filter...>> {};

template <typename Tuple, typename...Filter>
using filtered_tuple_t = typename filtered_tuple<Tuple, Filter...>::type;

///////////////////////////////////////
// Фильтрация тупла по списку индексов

template <typename...T, size_t...I>
auto make_filtered_tuple(const std::tuple<T...>& tuple, std::index_sequence<I...>)
   -> filtered_tuple_t<std::tuple<T...>, std::index_sequence<I...>>
{
   return { std::get<I>(tuple)... };
}

////////////////////////////////////
// Фильтрация тупла по списку типов

template <typename...Filter, typename...T>
auto make_filtered_tuple(const std::tuple<T...>& tuple)
    -> filtered_tuple_t<std::tuple<T...>, Filter...>
{
   return make_filtered_tuple(tuple, make_filtered_index_sequence<std::tuple<T...>, Filter...>{});
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Try to use

template <typename...T, size_t...I>
void print(const std::tuple<T...>& tuple, std::index_sequence<I...>)
{
   ((std::cout << std::get<I>(tuple) << ", "), ...) << std::endl;
}

template <typename...T>
void print(const std::tuple<T...>& tuple)
{
   print(tuple, std::index_sequence_for<T...>{});
}

int main()
{
   const auto tuple = std::make_tuple("Morning", 42, std::string("Hello"), 3.14, true, std::string("World"), 'A');

   print(make_filtered_tuple<int, std::string>(tuple)); // -> 42, Hello, World, 
   print(make_filtered_tuple<double, const char*, char>(tuple)); // -> Morning, 3.14, A, 
}


Если вывод типа конечного тупла в явном виде не нужен (filtered_tuple_t), можно без него.

http://coliru.stacked-crooked.com/a/f1cb953f87844449

  То же самое, но без вывода типа конечного тупла (чуть компактнее)
#include <iostream>
#include <string>
#include <tuple>
#include <utility>

template <typename T, typename...>
struct is_in_the_list : std::false_type {};

template <typename T, typename U, typename...X>
struct is_in_the_list<T, U, X...> : is_in_the_list<T, X...> {};

template <typename T, typename...X>
struct is_in_the_list<T, T, X...> : std::true_type {};

template <typename Tuple, typename FilterTuple, typename I = std::index_sequence<0>, typename = void>
struct filtered_index_sequence;

template <typename T, typename...S, typename...F, size_t I, size_t...J>
struct filtered_index_sequence<std::tuple<T, S...>, std::tuple<F...>, std::index_sequence<I, J...>,
   std::enable_if_t<is_in_the_list<T, F...>::value>> :
   filtered_index_sequence<std::tuple<S...>, std::tuple<F...>, std::index_sequence<I + 1, J..., I>> { };

template <typename T, typename...S, typename...F, size_t I, size_t...J>
struct filtered_index_sequence<std::tuple<T, S...>, std::tuple<F...>, std::index_sequence<I, J...>,
   std::enable_if_t<!is_in_the_list<T, F...>::value>> :
   filtered_index_sequence<std::tuple<S...>, std::tuple<F...>, std::index_sequence<I + 1, J...>> { };

template <typename...F, size_t I, size_t...J>
struct filtered_index_sequence<std::tuple<>, std::tuple<F...>, std::index_sequence<I, J...>>
{
   using type = std::index_sequence<J...>;
};

template <typename Tuple, typename...Filter>
using make_filtered_index_sequence = typename filtered_index_sequence<Tuple, std::tuple<Filter...>>::type;

template <typename...T, size_t...I>
auto make_filtered_tuple(const std::tuple<T...>& tuple, std::index_sequence<I...>)
{
   return std::make_tuple(std::get<I>(tuple)...);
}

template <typename...Filter, typename...T>
auto make_filtered_tuple(const std::tuple<T...>& tuple)
{
   return make_filtered_tuple(tuple, make_filtered_index_sequence<std::tuple<T...>, Filter...>{});
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Try to use

template <typename...T, size_t...I>
void print(const std::tuple<T...>& tuple, std::index_sequence<I...>)
{
   ((std::cout << std::get<I>(tuple) << ", "), ...) << std::endl;
}

template <typename...T>
void print(const std::tuple<T...>& tuple)
{
   print(tuple, std::index_sequence_for<T...>{});
}

int main()
{
   const auto tuple = std::make_tuple("Morning", 42, std::string("Hello"), 3.14, true, std::string("World"), 'A');

   print(make_filtered_tuple<int, std::string>(tuple)); // -> 42, Hello, World, 
   print(make_filtered_tuple<double, const char*, char>(tuple)); // -> Morning, 3.14, A, 
}
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 22.02.2023 13:44 rg45 . Предыдущая версия . Еще …
Отредактировано 22.02.2023 12:03 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 11:00 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 10:46 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 10:45 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 10:42 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 10:19 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 3:47 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 3:47 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 3:28 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 2:44 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 2:41 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 2:21 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 2:17 rg45 . Предыдущая версия .
Re: Создать новый std::tuple из подмножества имеющегося
От: so5team https://stiffstream.com
Дата: 22.02.23 05:22
Оценка: 21 (3)
Здравствуйте, SaZ, Вы писали:

SaZ>Ломаю голову над следующей задачей. Дан некий тупл, нужно получить новый, в котором будут только определённые типы (например, унаследованные от некоего my_base класса).

SaZ>Как это сделать?

Нечто подобное в рамках C++14 мы делали в RESTinio (объяснение можно найти здесь, раздел "Один из трюков при обработке параметров path_to_params/path_to_tuple").
Re: Создать новый std::tuple из подмножества имеющегося
От: Voivoid Россия  
Дата: 22.02.23 08:10
Оценка: 6 (1)
Здравствуйте, SaZ, Вы писали:

SaZ>Всем добра,


SaZ>Ломаю голову над следующей задачей. Дан некий тупл, нужно получить новый, в котором будут только определённые типы (например, унаследованные от некоего my_base класса).

SaZ>Как это сделать?
Ну или еще как вариант просто взять boost::fusion ( https://www.boost.org/doc/libs/1_81_0/libs/fusion/doc/html/fusion/view/filter_view.html или https://www.boost.org/doc/libs/1_81_0/libs/fusion/doc/html/fusion/algorithm/transformation/metafunctions/filter_if.html ). С std::tuple fusion тоже работает ( надо только header дополнительный подключить ).
Re[3]: Создать новый std::tuple из подмножества имеющегося
От: rg45 СССР  
Дата: 22.02.23 12:46
Оценка: 2 (1)
Здравствуйте, SaZ, Вы писали:

SaZ>Спасибо, то что надо, но я никак не могу осилить enable_if:


SaZ>
SaZ>// template <typename T, std::enable_if_t<std::is_convertible_v<T*, my_base *>, bool> = true>
SaZ>// template <typename T, typename = std::enable_if_t<std::is_base_of_v<my_base, T>, bool>>
SaZ>template <typename T, std::enable_if_t<std::is_base_of_v<my_base, T>, bool> = true>
SaZ>auto my_filter(const T& v)
SaZ>{
SaZ>    return std::make_tuple(v);
SaZ>}
SaZ>


SaZ>Эта перегрузка никогда не вызывается. Хотя пробовал static_assert вне этой функции — всё ок.


Сама по себе функция написана правильно. Не вызываться она может по разным причинам:


Могут быть и другие варианты. Чтоб диагностировать точно, желательно видеть минимальный пример, воспроиводящий проблему.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 22.02.2023 13:28 rg45 . Предыдущая версия . Еще …
Отредактировано 22.02.2023 13:16 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 13:15 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 13:00 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 12:53 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 12:52 rg45 . Предыдущая версия .
Отредактировано 22.02.2023 12:48 rg45 . Предыдущая версия .
Re: Создать новый std::tuple из подмножества имеющегося
От: SaZ  
Дата: 22.02.23 14:06
Оценка:
Всем большое спасибо за подробные ответы с примерами. Всё получилось. Буду дальше развивать задачу и изучать метапрограммирование.
Re: Создать новый std::tuple из подмножества имеющегося
От: vopl Россия  
Дата: 22.02.23 15:26
Оценка: 17 (2)
Здравствуйте, SaZ, Вы писали:

SaZ>Всем добра,


SaZ>Ломаю голову над следующей задачей. Дан некий тупл, нужно получить новый, в котором будут только определённые типы (например, унаследованные от некоего my_base класса).

SaZ>Как это сделать?
SaZ>Тупл отдаётся сторонней библиотекой. Хочу написать враппер, который возвращает новый. К примеру, из исходного тупла нужно получить новый в котором будут только строки и целые числа:
SaZ>
SaZ>std::tuple<int, string, bool, string, float> -> как получить? -> std::tuple<int, string, string>;
SaZ>

SaZ>Типы заранее я не знаю, их будут определять пользователи фреймворка.

SaZ>Контекст следующий, но не думаю что это важно: использую sqlite_orm, нужно автоматизировать создание некоторых таблиц на основе имеющихся, но с модификациями. В этом фреймворке типы нужно указать во время компиляции.

SaZ>In-memory sqlite используется в качестве внутренней структуры данных при работе gui приложения. Хочу прикрутить автоматическое ведение истории изменений и автоматизировать создание undo/redo команд.

такой же способ что и у rg45, только чуть другим способом
для паттерн мачинга, вместо специализаций используется перегрузка и if constexpr
для выделения элементов из паков используются шаблонные лямбды по месту
вместо std::integer_sequence используется собственный VList, но это только для наглядности, особого смысла в этом нет
в общем, для образовательных целей, думаю, эту штуку вполне можно использовать

#include <iostream>
#include <tuple>
#include <type_traits> // std::decay, std::is_same
#include <utility>     // std::forward

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
// помогалки обобщенные
// списки типов и значений
template <class...> struct TList {};
template <auto...> struct VList {};

// генератор индексов
template <class... T>
using indicesFor = decltype([]<std::size_t... v>(std::index_sequence<v...>){ return VList<v...>{}; }(std::index_sequence_for<T...>{}));

// перегрузка, для паттерн-мачинга
template <class... Fs>
constexpr auto overload(Fs&&... fs)
{
    struct Combine : std::decay_t<Fs>...
    {
        constexpr Combine(Fs&&... fs) : std::decay_t<Fs>{std::forward<Fs&&>(fs)}... {}
        using std::decay_t<Fs>::operator()...;
    };

    return Combine{std::forward<Fs&&>(fs)...};
}

//стырил у rg45)
template <class... T>
void print(const std::tuple<T...>& tuple)
{
    [&]<std::size_t... idx>(VList<idx...>) {
        ((std::cout << (idx ? ", " : "") << std::get<idx>(tuple)), ...) << std::endl;
    }(indicesFor<T...>{});
}

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
// помогалки частные
// выдает истину если T присутствует в чеклисте
template <class T, class... Checklist>
constexpr bool isAllowed()
{
    return overload([](TList<>) constexpr {
        return false;
    }, []<class ChecklistHead, class... ChecklistTail>(TList<ChecklistHead, ChecklistTail...>) constexpr {
        if constexpr(std::is_same_v<T, ChecklistHead>)
            return true;
        else
            return isAllowed<T, ChecklistTail...>();
    })(TList<Checklist...>{});
}

// выбирает индексы для которых установлено разрешение
template <std::size_t... src, bool... allow, std::size_t... dst>
auto selectIndices(VList<src...> srcSeq, VList<allow...> allowSeq, VList<dst...> = {})
{
    return overload([](VList<>, VList<>) {
        return VList<dst...>{};
    }, []<std::size_t srcHead, std::size_t... srcTail, bool allowHead, bool...allowTail>(VList<srcHead, srcTail...>, VList<allowHead, allowTail...>) {
        if constexpr(allowHead)
            return selectIndices(
                VList<srcTail...>{},
                VList<allowTail...>{},
                VList<dst..., srcHead>{});
        else
            return selectIndices(
                VList<srcTail...>{},
                VList<allowTail...>{},
                VList<dst...>{});
    })(srcSeq, allowSeq);
}

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
// собственно, решение
// формируем набор буль-флажков для кажного типа в тюпле
// по полученным флажкам выбираем индексы
// по индексам рекомбинируем тюпл
template <class... Allowed, class... T>
auto filter(const std::tuple<T...>& src)
{
    return [&]<std::size_t... idx>(VList<idx...>) {
        return std::tuple{std::get<idx>(src)...};
    }(selectIndices(indicesFor<T...>{}, VList<isAllowed<T, Allowed...>()...>{}));
}

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
int main()
{
    const auto tuple = std::tuple{"Morning", 42, std::string("Hello"), 3.14, true, std::string("World"), 'A'};

    print(filter<int, std::string>(tuple)); // -> 42, Hello, World
    print(filter<double, const char*, char>(tuple)); // -> Morning, 3.14, A

    return 0;
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.