Как узнать хранит ли variant...
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 19.04.25 18:33
Оценка:
Здравствуйте!

Как узнать хранит ли variant шаблонный тип, где тип/типы, которыми инстанциирован шаблон, мне неизвестны?

Есть шаблонный функтор вида

template<typename InputIteratorType, typename OutputIteratorType>
using BasicFormatValueFilter = std::function< OutputIteratorType( InputIteratorType  // begin
                                                                , InputIteratorType  // end
                                                                , OutputIteratorType
                                                                )
                                            >;


Есть специализация
using FormatValueFilter = BasicFormatValueFilter< marty::utf::UtfInputIterator<char>
                                                , marty::utf::UtfOutputIterator<char>
                                                >;


Есть стандартный вариант
  FormatArgumentVariant
using FormatArgumentVariant = 
    std::variant< bool
                , char
                , unsigned char        // std::uint8_t
                , signed char          // std::int8_t
                , unsigned short       // std::uint16_t
                , signed short         // std::int16_t
                , unsigned int         // std::uint32_t
                , int                  // std::int32_t
                , unsigned long        // 32/64, it's depends
                , long                 // 32/64, it's depends
                , unsigned long long   // std::uint64_t
                , long long            // std::int64_t
                , float
                , double
                , long double
                , const char*
                , const wchar_t*
                , std::string
                , std::wstring
                , marty::Decimal
                , FormatValueFilter
                >;



Есть моя шаблонная функция, которая принимает контейнер вариантов, но там могут лежать не мои стандартные варианты, а пользовательские, в тч в пользовательском варианте могут лежать как функторы FormatValueFilter, так пользовательские типы функторов на базе BasicFormatValueFilter.

Вопросы такие.

1. Мне надо узнать, есть ли в варианте, который хранит контейнер, что-либо на базе BasicFormatValueFilter, и получить его точный тип
2. Проверить, является ли текущее значение варианта экземпляром функтора

Сначала был второй вопрос, но пока размышлял, появился и первый, и если по второму есть кой-какие идеи, то по первому что-то ничего в голову не приходит
Маньяк Робокряк колесит по городу
Отредактировано 19.04.2025 18:35 Marty . Предыдущая версия .
Re: Как узнать хранит ли variant...
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 19.04.25 21:00
Оценка:
Здравствуйте, Marty, Вы писали:

Попробовал спросить у гопоты

  Промпт
Вопрос по языку C++ и типам std::function и std::variant.

У меня есть свой шаблонный тип функтора:

template<typename InputIteratorType, typename OutputIteratorType>
using BasicFormatValueFilter = std::function< OutputIteratorType( InputIteratorType  // begin
                                                                , InputIteratorType  // end
                                                                , OutputIteratorType
                                                                )
                                            >;

Танный тип функтора может быть специализирован любыми типами
InputIteratorType и OutputIteratorType.

Требуется определить, содержится ли какая-либо специализация
шаблона BasicFormatValueFilter в типе, определённом с использованием
std::variant.

Также необходимо определить типы, которыми был специализирован шаблон
BasicFormatValueFilter.

Ответы гопоты

  chat.deepseek.com
Для решения вашей задачи нужно определить, содержится ли в std::variant хотя бы один тип, который является специализацией шаблона BasicFormatValueFilter, а также извлечь параметры шаблона (InputIteratorType и OutputIteratorType) для таких специализаций.

Шаг 1: Проверка, является ли тип специализацией BasicFormatValueFilter

Для этого можно использовать метапрограммирование с помощью std::is_same и частичной специализации шаблонов.

#include <type_traits>
#include <functional>
#include <variant>

// Шаблон для проверки, является ли тип специализацией BasicFormatValueFilter
template<typename>
struct is_basic_format_value_filter : std::false_type {};

template<typename InputIt, typename OutputIt>
struct is_basic_format_value_filter<
    std::function<OutputIt(InputIt, InputIt, OutputIt)>
> : std::true_type {};

// Удобная обёртка для проверки
template<typename T>
inline constexpr bool is_basic_format_value_filter_v = 
    is_basic_format_value_filter<T>::value;


Шаг 2: Определение параметров InputIteratorType и OutputIteratorType

Если тип является специализацией BasicFormatValueFilter, можно извлечь его параметры:

template<typename>
struct basic_format_value_filter_traits;

template<typename InputIt, typename OutputIt>
struct basic_format_value_filter_traits<
    std::function<OutputIt(InputIt, InputIt, OutputIt)>
> {
    using input_iterator_type = InputIt;
    using output_iterator_type = OutputIt;
};


Шаг 3: Проверка всех типов в std::variant

Теперь нужно проверить все типы в std::variant и найти среди них специализации BasicFormatValueFilter. Для этого можно использовать std::variant и рекурсивные шаблонные метафункции.

Вариант 1: Проверка, содержит ли variant хотя бы один такой тип

template<typename Variant>
struct variant_contains_basic_format_value_filter;

template<typename... Ts>
struct variant_contains_basic_format_value_filter<std::variant<Ts...>> 
    : std::disjunction<is_basic_format_value_filter<Ts>...> {};

template<typename Variant>
inline constexpr bool variant_contains_basic_format_value_filter_v =
    variant_contains_basic_format_value_filter<Variant>::value;

Вариант 2: Получение списка всех BasicFormatValueFilter в variant

Если нужно получить список всех таких типов, можно использовать std::tuple для их хранения:

template<typename... Ts>
struct get_basic_format_value_filters {
    using type = decltype(std::tuple_cat(
        std::conditional_t<
            is_basic_format_value_filter_v<Ts>,
            std::tuple<Ts>,
            std::tuple<>
        >{}...
    ));
};

template<typename... Ts>
using get_basic_format_value_filters_t = 
    typename get_basic_format_value_filters<Ts...>::type;


Пример использования


#include <iostream>

int main() {
    using MyVariant = std::variant<
        int,
        std::function<int*(const int*, const int*, int*)>,
        double,
        std::function<char*(const char*, const char*, char*)>
    >;

    // Проверка, содержит ли variant хотя бы один BasicFormatValueFilter
    constexpr bool contains = variant_contains_basic_format_value_filter_v<MyVariant>;
    std::cout << "Contains BasicFormatValueFilter: " << contains << "\n";

    // Получение списка всех BasicFormatValueFilter в variant
    using Filters = get_basic_format_value_filters_t<
        int,
        std::function<int*(const int*, const int*, int*)>,
        double,
        std::function<char*(const char*, const char*, char*)>
    >;

    static_assert(std::tuple_size_v<Filters> == 2, "Should have 2 filters");

    // Извлечение параметров первого фильтра
    using FirstFilter = std::tuple_element_t<0, Filters>;
    using Traits = basic_format_value_filter_traits<FirstFilter>;
    using InputIt = Traits::input_iterator_type;
    using OutputIt = Traits::output_iterator_type;

    static_assert(std::is_same_v<InputIt, const int*>, "InputIt should be const int*");
    static_assert(std::is_same_v<OutputIt, int*>, "OutputIt should be int*");

    return 0;
}


Вывод


variant_contains_basic_format_value_filter_v<Variant> — проверяет, содержит ли Variant хотя бы одну специализацию BasicFormatValueFilter.

get_basic_format_value_filters_t<Ts...> — возвращает std::tuple со всеми специализациями BasicFormatValueFilter среди Ts....

basic_format_value_filter_traits<Filter> — позволяет извлечь InputIteratorType и OutputIteratorType из специализации BasicFormatValueFilter.

Этот подход даёт гибкость в анализе содержимого std::variant и работе с шаблонными типами.

  www.perplexity.ai

Обнаружение и Анализ Специализаций BasicFormatValueFilter в std::variant

В данном отчете представлено подробное решение задачи по определению наличия специализаций шаблонного типа функтора в std::variant и извлечению типов этих специализаций с использованием техник метапрограммирования в C++.

Проверка наличия специализации BasicFormatValueFilter в std::variant

Для определения, содержится ли какая-либо специализация шаблона BasicFormatValueFilter в std::variant, необходимо разработать механизм распознавания этого типа. Техника, известная как SFINAE (Substitution Failure Is Not An Error), позволяет нам создать trait-класс для проверки типов.

Базовая реализация trait-класса для определения типа

Сначала определим trait, который будет проверять, является ли тип специализацией BasicFormatValueFilter:

// Проверка, является ли тип специализацией BasicFormatValueFilter
template<typename T>
struct is_basic_format_value_filter : std::false_type {};

// Специализация для BasicFormatValueFilter
template<typename InputIteratorType, typename OutputIteratorType>
struct is_basic_format_value_filter<
    std::function<OutputIteratorType(
        InputIteratorType, InputIteratorType, OutputIteratorType)>> 
    : std::true_type {};

// Вспомогательная переменная для упрощения использования
template<typename T>
inline constexpr bool is_basic_format_value_filter_v = 
    is_basic_format_value_filter<T>::value;


Эта реализация специализирует trait для точного типа функтора, соответствующего шаблону BasicFormatValueFilter.

Проверка содержимого std::variant

Теперь создадим метафункцию для проверки, содержит ли std::variant какую-либо специализацию BasicFormatValueFilter:

// Метафункция для проверки std::variant
template<typename Variant>
struct variant_contains_basic_format_value_filter;

// Специализация для std::variant
template<typename... Ts>
struct variant_contains_basic_format_value_filter<std::variant<Ts...>> {
    static constexpr bool value = (is_basic_format_value_filter_v<Ts> || ...);
};

// Вспомогательная переменная
template<typename Variant>
inline constexpr bool variant_contains_basic_format_value_filter_v = 
    variant_contains_basic_format_value_filter<Variant>::value;


Эта метафункция использует fold-expression (... || ...) для проверки каждого типа в std::variant на соответствие нашему критерию.

Извлечение типов из специализации BasicFormatValueFilter

После обнаружения специализации нам требуется извлечь типы, которыми был специализирован шаблон BasicFormatValueFilter.

Trait для извлечения типов специализации

// Trait для извлечения типов из BasicFormatValueFilter
template<typename T>
struct basic_format_value_filter_traits {
    static constexpr bool is_basic_format_value_filter = false;
    // Пустые типы по умолчанию
};

// Специализация для извлечения типов
template<typename InputIteratorType, typename OutputIteratorType>
struct basic_format_value_filter_traits<
    std::function<OutputIteratorType(
        InputIteratorType, InputIteratorType, OutputIteratorType)>> {
    static constexpr bool is_basic_format_value_filter = true;
    using input_iterator_type = InputIteratorType;
    using output_iterator_type = OutputIteratorType;
};

Этот trait-класс позволяет извлечь типы из специализации BasicFormatValueFilter и предоставить к ним доступ через псевдонимы типов.

Поиск специализаций внутри std::variant

Для извлечения типов из первой найденной специализации внутри std::variant можно использовать следующий подход:

// Рекурсивный поиск первой специализации в пакете типов
template<typename... Ts>
struct find_first_basic_format_value_filter;

// Базовый случай: пустой пакет
template<>
struct find_first_basic_format_value_filter<> {
    static constexpr bool found = false;
    using type = void;
};

// Рекурсивный случай
template<typename T, typename... Rest>
struct find_first_basic_format_value_filter<T, Rest...> {
private:
    using next = find_first_basic_format_value_filter<Rest...>;
public:
    static constexpr bool found = is_basic_format_value_filter_v<T> ? 
                                  true : next::found;
    using type = std::conditional_t<is_basic_format_value_filter_v<T>, 
                                  T, typename next::type>;
};

// Извлечение типов из std::variant
template<typename Variant>
struct extract_first_basic_format_value_filter;

template<typename... Ts>
struct extract_first_basic_format_value_filter<std::variant<Ts...>> {
    using find_result = find_first_basic_format_value_filter<Ts...>;
    static constexpr bool found = find_result::found;
    using type = typename find_result::type;
    
    template<typename U>
    using extract_traits = basic_format_value_filter_traits<U>;
    
    using traits = std::conditional_t<found, 
                                    extract_traits<type>, 
                                    basic_format_value_filter_traits<void>>;
    
    using input_iterator_type = typename traits::input_iterator_type;
    using output_iterator_type = typename traits::output_iterator_type;
};

Эта реализация рекурсивно перебирает типы в std::variant и находит первую специализацию BasicFormatValueFilter, затем извлекает соответствующие типы.

Пример использования

Приведём пример использования наших метафункций:

#include <iostream>
#include <type_traits>
#include <functional>
#include <variant>
#include <string>
#include <vector>

// Определение BasicFormatValueFilter
template<typename InputIteratorType, typename OutputIteratorType>
using BasicFormatValueFilter = std::function<OutputIteratorType(
    InputIteratorType, InputIteratorType, OutputIteratorType)>;

// Здесь идут наши метафункции, определённые выше

int main() {
    // Пример std::variant с специализацией BasicFormatValueFilter
    using VariantType = std::variant<
        int, 
        double, 
        BasicFormatValueFilter<std::string::iterator, std::vector<char>::iterator>,
        std::string
    >;
    
    // Проверка наличия специализации
    constexpr bool has_filter = variant_contains_basic_format_value_filter_v<VariantType>;
    std::cout << "Variant contains BasicFormatValueFilter: " 
              << (has_filter ? "true" : "false") << std::endl;
    
    // Извлечение типов
    using ExtractResult = extract_first_basic_format_value_filter<VariantType>;
    std::cout << "Found filter: " << (ExtractResult::found ? "true" : "false") << std::endl;
    
    // Использование извлечённых типов
    if constexpr (ExtractResult::found) {
        // Для примера, просто выведем информацию о том, что мы нашли
        std::cout << "Input iterator is std::string::iterator: " 
                  << std::is_same_v<ExtractResult::input_iterator_type, std::string::iterator> 
                  << std::endl;
        std::cout << "Output iterator is std::vector<char>::iterator: " 
                  << std::is_same_v<ExtractResult::output_iterator_type, std::vector<char>::iterator> 
                  << std::endl;
    }
    
    return 0;
}


Заключение

В данном отчете были представлены решения для двух ключевых задач:

Определение наличия специализации шаблона BasicFormatValueFilter в std::variant с использованием метапрограммирования шаблонов.

Извлечение типов, которыми специализирован шаблон BasicFormatValueFilter.

Реализованные метафункции основаны на концепциях частичной специализации шаблонов и SFINAE, что является мощным инструментом для статического анализа типов в C++. Предложенные решения работают во время компиляции и не требуют выполнения кода, что соответствует метапрограммному подходу.

Для более сложных сценариев, например, извлечения всех специализаций BasicFormatValueFilter из std::variant, потребовались бы более сложные метапрограммные конструкции, такие как типы-списки или типы-последовательности, однако представленное решение покрывает большинство практических случаев использования.

Очень похоже на годноту, пошел раскуривать.

Решение от дипсика выглядит попроще
Маньяк Робокряк колесит по городу
Re: Как узнать хранит ли variant...
От: rg45 СССР  
Дата: 20.04.25 06:51
Оценка:
Здравствуйте, Marty, Вы писали:

M>Здравствуйте!


M>Как узнать хранит ли variant шаблонный тип, где тип/типы, которыми инстанциирован шаблон, мне неизвестны?


M>Есть шаблонный функтор вида


M>
M>template<typename InputIteratorType, typename OutputIteratorType>
M>using BasicFormatValueFilter = std::function< OutputIteratorType( InputIteratorType  // begin
M>                                                                , InputIteratorType  // end
M>                                                                , OutputIteratorType
M>                                                                )
                                            >>;
M>


Ну, узнать можно через std::visit + функциональный класс с перегруженными функциональными операторами, часть из которых (или даже все) могут быть шаблонными.

Но только твой BasicFormatValueFilter — это же просто алиас, хоть и шаблонный. Его невозможно отличить от какого-нибудь совершенно левого шаблонного объявленния, сделанного похожим образом.

template<typename Foo, typename Bar>
using Baz = std::function<Bar(Foo, Foo, Bar)>;

static_assert(std::same_as<
   Baz<int, int, double>
,  BasicFormatValueFilter<int, int, double>
>);



P.S. И вообще, объявление, подобное BasicFormatValueFilter, выгдлядит странным для меня. Обычно std::function используют тогда, когда нужно перейти от шаблонных функционалов к функционалам с фиксированными типами формальных параметров и возвращаемого значения. И превращать это дело опять в шаблон — ну, это что-то совсем странное для меня. Ведь наложить на шаблоныый функционал какие-то констрейнты (хоть с концептами, хоть со SFINAE) можно, не прибегая к использованию std::function с её скрытым виртуальным вызовом.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 20.04.2025 7:11 rg45 . Предыдущая версия . Еще …
Отредактировано 20.04.2025 7:10 rg45 . Предыдущая версия .
Отредактировано 20.04.2025 7:08 rg45 . Предыдущая версия .
Отредактировано 20.04.2025 7:04 rg45 . Предыдущая версия .
Отредактировано 20.04.2025 7:03 rg45 . Предыдущая версия .
Отредактировано 20.04.2025 6:56 rg45 . Предыдущая версия .
Re[2]: Как узнать хранит ли variant...
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 20.04.25 11:26
Оценка:
Здравствуйте, rg45, Вы писали:

R>Но только твой BasicFormatValueFilter — это же просто алиас, хоть и шаблонный. Его невозможно отличить от какого-нибудь совершенно левого шаблонного объявленния, сделанного похожим образом.


Именно. Пользователь может определять свой тип фильтра, и свои типы Input/Output итераторов


R>P.S. И вообще, объявление, подобное BasicFormatValueFilter, выгдлядит странным для меня. Обычно std::function используют тогда, когда нужно перейти от шаблонных функционалов к функционалам с фиксированными типами формальных параметров и возвращаемого значения. И превращать это дело опять в шаблон — ну, это что-то совсем странное для меня. Ведь наложить на шаблоныый функционал какие-то констрейнты (хоть с концептами, хоть со SFINAE) можно, не прибегая к использованию std::function с её скрытым виртуальным вызовом.


Ну, я хотел сделать фильтры, для преобразования выводимых строк после их форматирования, например, html/xml-escape, фильтры должны уметь передаваться как аргумент при вызове функции форматирования, наравне с самими форматируемыми величинами. А потом, внутри реализации, я решил присунуть стандартные фильтры, которые создаёт фабрика стандартных фильтров, которая задаётся параметром шаблона и возвращает такой же функтор, который может лежать в параметрах шаблона. Но конкретный тип функтора я не знаю, пользователь может не использовать мои алиасы. В аргументы стандартный фильтр я не могу запихивать, и я решил завести вектор фильтров, куда кладу либо созданный фабрикой стандартный фильтр, либо взятый из аргументов. А для этого мне надо знать тип функтора. Вот, как-то так. Пока даже что-то вырисовывается
Маньяк Робокряк колесит по городу
Re[3]: Как узнать хранит ли variant...
От: rg45 СССР  
Дата: 06.05.25 08:30
Оценка: 2 (1)
Здравствуйте, Marty, Вы писали:

M>Ну, я хотел сделать фильтры, для преобразования выводимых строк после их форматирования, например, html/xml-escape, фильтры должны уметь передаваться как аргумент при вызове функции форматирования, наравне с самими форматируемыми величинами. А потом, внутри реализации, я решил присунуть стандартные фильтры, которые создаёт фабрика стандартных фильтров, которая задаётся параметром шаблона и возвращает такой же функтор, который может лежать в параметрах шаблона. Но конкретный тип функтора я не знаю, пользователь может не использовать мои алиасы. В аргументы стандартный фильтр я не могу запихивать, и я решил завести вектор фильтров, куда кладу либо созданный фабрикой стандартный фильтр, либо взятый из аргументов. А для этого мне надо знать тип функтора. Вот, как-то так. Пока даже что-то вырисовывается


Извини, что отвечаю с задержкой. Вот примерный эскиз. Использую концепты — просто потому что так компактнее и нагляднее. Но переложить на SFINAE — это лишь дело техники.

https://coliru.stacked-crooked.com/a/0a9e5a5ac7ee0af7

#include <iostream>
#include <functional>
#include <type_traits>
#include <variant>

/////////////////////////////////////////////////////////////////////////////////////////
// Вспомогательные инструменты

template <typename...T>
struct Inherited : std::decay_t<T>... {};

template <typename> struct IsFreeFunction : std::false_type{};
template<typename R, typename...A> struct IsFreeFunction<R(A...)> : std::true_type{};
template <typename T> struct IsFreeFunction<T*> : IsFreeFunction<T>{};

template <typename T>
concept FreeFunction = IsFreeFunction<std::decay_t<T>>::value;

template <typename T>
concept CallableObject =
   std::is_class_v<std::decay_t<T>>
   and not requires { &Inherited<T, decltype([]{})>::operator(); };

template <typename T>
concept Callable = FreeFunction<T> or CallableObject<T>;

template<CallableObject...T>
struct Overload : T... { using T::operator()...; };

template<CallableObject...T>
Overload(T&&...) -> Overload<std::decay_t<T>...>;

/////////////////////////////////////////////////////////////////////////////////////////
// Использование

template<typename InputIteratorType, typename OutputIteratorType>
using BasicFormatValueFilter = std::function <
   OutputIteratorType(
      InputIteratorType  // begin
   ,  InputIteratorType  // end
   ,  OutputIteratorType
   )
>;

/////////////////////////////////////////////////////////////////////////////////////////
// Является ли текущее значение варианта экземпляром функтора

template <typename...T>
bool IsCallable(const std::variant<T...>& v) {
   return std::visit(
      Overload(
         [](auto&&...) {return false;}
      ,  [](Callable auto&&) {return true;}
      )
   ,  v
   );
}

/////////////////////////////////////////////////////////////////////////////////////////
// Аналогично: есть ли что - либо на базе BasicFormatValueFilter

template <typename...T>
bool IsBasicFormatValueFilter(const std::variant<T...>& v) {
   return std::visit(
      Overload(
         [](auto&&...) {return false;}
      ,  []<typename I, typename O>(const BasicFormatValueFilter<I, O>&) {
            // А вот получить точный тип содержимого ты можешь только здесь, внутри специальной функции
            return true;
         }
      )
   ,  v
   );
}

int main()
{
   using CustomInputIterator = const int*;
   using CustomOutputIterator = double*;
   using CustomFormatValueFilter = BasicFormatValueFilter<CustomInputIterator, CustomOutputIterator>;

   using V = std::variant<int, double, CustomFormatValueFilter, std::function<double(double)>>;

   std::cout << std::boolalpha;

   std::cout << IsCallable(V{3.14}) << std::endl;                          // false
   std::cout << IsCallable(V{[](double x){return x * 10;}}) << std::endl;  // true

   std::cout << "--" << std::endl;
   auto customFilter = [](CustomInputIterator, CustomInputIterator, CustomOutputIterator) {
      return CustomOutputIterator{};
   };

   std::cout << IsBasicFormatValueFilter(V{3.14}) << std::endl;            // false
   std::cout << IsBasicFormatValueFilter(V{3.14}) << std::endl;            // false
   std::cout << IsBasicFormatValueFilter(V{customFilter}) << std::endl;    // true
}
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 06.05.2025 11:24 rg45 . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.