Вариадики и std::visit
От: SaZ  
Дата: 28.10.20 00:35
Оценка:
Всем доброго времени суток,

Я тут пытаюсь раздуплиться с тем, как скрестить std::variant, std::visit и вариадики, что-то вообще нет мыслей.
Код есть тут: https://wandbox.org/permlink/WGQdh4FV7a1k8zGi

  Тыц
#include <cassert>
#include <variant>
#include <type_traits>
#include <memory>
#include <iostream>


struct IFace
{
    virtual void foo() = 0;
};

template<typename TKey, typename TValue>
struct Base : IFace
{
    using Key = TKey;
    using Value = TValue;
};

struct Int : Base<int, Int>
{
    void foo() override { std::cout << "Int"; }
};

struct Double : Base<double, Double>
{
    void foo() override { std::cout << "Double"; }
};

using Var = std::variant<int, double>;

template<typename ...Args>
std::shared_ptr<IFace> factory(const Var& v)
{
    std::shared_ptr<IFace> p;

    std::visit([&p](auto&& arg){
        using TKey = std::decay_t<decltype(arg)>;
        // TODO: use variadic instead of hardcoded types
        if constexpr (std::is_same_v<TKey, int>)
        {
            using TValue = typename Base<TKey, Int>::Value;
            p = std::make_shared<TValue>();
            std::cout << "int ";
        }
        else if constexpr (std::is_same_v<TKey, double>)
        {
            using TValue = typename Base<TKey, Double>::Value;
            p = std::make_shared<TValue>();
            std::cout << "double ";
        }
    }, v);

    return p;
}


int main()
{
    const Var v = 42.;
    auto p = factory<Int, Double>(v);

    assert(p != nullptr);
    p->foo();

    return 0;
}


Подскажите, в какую сторону копать?
Re: Вариадики и std::visit
От: watchmaker  
Дата: 28.10.20 01:16
Оценка: 2 (1) +1
Здравствуйте, SaZ, Вы писали:


SaZ>Я тут пытаюсь раздуплиться с тем, как скрестить std::variant, std::visit и вариадики, что-то вообще нет мыслей.

SaZ>Подскажите, в какую сторону копать?


Судя по комментарию // TODO: use variadic instead of hardcoded types проблема совсем не в вариадиках и не в std::visit.
А проблема в том, что нужно как-то сообщить компилятору, что если аргумент имеет тип int, то нужно создать экземпляр Int, а если имеет тип double, то нужно создать экземпляр Double, и.т.п.
Из кода, где объявлены struct Int, struct Double, информацию о такой взаимосвязи между типами получить, конечно, нельзя. Хотя бы потому, что где-нибудь можно написать struct Int2 : Base<int, Int2> {...}, и тогда будет совершенно непонятно что нужно создавать в ответ на int — экземпляр Int или Int2 ?

Поэтому это только твоя задача описать правила поведения в каждом случае. Можно кодом (как сейчас), но это неудобно. Другой вариант — использовать type traits, как, собственно, для кучи всего в с++ и сделано уже:

// общее правило отображение типа Src в Dst 
template <class Src>
struct TypeMap;

// конкретные специализации
 
template <>
struct TypeMap<int> {
    using Dst = Int;
};

template <>
struct TypeMap<double> {
    using Dst = Double;
};



Ну а визитор без условий на основе этого уже тривиально делается:
p = std::make_shared<typename TypeMap<TKey>::Dst>();
Re: Вариадики и std::visit
От: Chorkov Россия  
Дата: 28.10.20 10:06
Оценка: 6 (1) +1
Здравствуйте, SaZ, Вы писали:

SaZ>Всем доброго времени суток,


SaZ>Я тут пытаюсь раздуплиться с тем, как скрестить std::variant, std::visit и вариадики, что-то вообще нет мыслей.

SaZ>Код есть тут: https://wandbox.org/permlink/WGQdh4FV7a1k8zGi

SaZ>Подскажите, в какую сторону копать?


Я правильно понял, что аргументы в вызове factoty<...> это типы, которые должны быть ассоциированы с соответствующими (по порядку следования) типами в аргументах std::variant (Var) ?
Или это просто список, а типы которым они должны соответствовать, берем из описания Base?

Тогда выделите визиотр-фабрику в отделительный класс, специализируемый для каждого финального класса:
template<typename T>
struct factory_for_type
{
    std::shared_ptr<IFace> operator() (typename T::Key key) const 
    { 
        return  std::make_shared<T>(key);
    }
};


собрать визитор из списка типов — через наследование:

template<typename ...Args>
std::shared_ptr<IFace> factory(const Var& v)
{
    struct visitor 
        : factory_for_type<Args>... 
    {
        using factory_for_type<Args>::operator()...;
    };

    return std::visit( visitor{}, v );
}


Если визиторы невозможно написать без связывания локальных переменных, предется использовать мене читаемое решение:

// from https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

template<typename ...Args>
std::shared_ptr<IFace> factory(const Var& v)
{
    ....
    std::visit( overloaded{
         [&p]( typename Args::Key key ) 
         { 
            p = std::make_shared<Args>(key);
         } ...
    }, v); 
    ...
}




[/ccode]
Re[2]: Вариадики и std::visit
От: SaZ  
Дата: 28.10.20 21:23
Оценка:
Здравствуйте, watchmaker, Вы писали:

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



SaZ>>Я тут пытаюсь раздуплиться с тем, как скрестить std::variant, std::visit и вариадики, что-то вообще нет мыслей.

SaZ>>Подскажите, в какую сторону копать?


W>Судя по комментарию // TODO: use variadic instead of hardcoded types проблема совсем не в вариадиках и не в std::visit.

W>А проблема в том, что нужно как-то сообщить компилятору, что если аргумент имеет тип int, то нужно создать экземпляр Int, а если имеет тип double, то нужно создать экземпляр Double, и.т.п.
W>Из кода, где объявлены struct Int, struct Double, информацию о такой взаимосвязи между типами получить, конечно, нельзя. Хотя бы потому, что где-нибудь можно написать struct Int2 : Base<int, Int2> {...}, и тогда будет совершенно непонятно что нужно создавать в ответ на int — экземпляр Int или Int2 ?

W>Поэтому это только твоя задача описать правила поведения в каждом случае. Можно кодом (как сейчас), но это неудобно. Другой вариант — использовать type traits, как, собственно, для кучи всего в с++ и сделано уже:

W>// общее правило отображение типа Src в Dst 
W>template <class Src>
W>struct TypeMap;

W>// конкретные специализации
 
W>template <>
W>struct TypeMap<int> {
W>    using Dst = Int;
W>};

W>template <>
W>struct TypeMap<double> {
W>    using Dst = Double;
W>};
W>



W>Ну а визитор без условий на основе этого уже тривиально делается:

W>
W>p = std::make_shared<typename TypeMap<TKey>::Dst>();
W>


И да и нет. Я хотел чтобы регистрация в фабрике выполнялась через crtp. В соседнем сообщении привели пример для моей задачи.
Re[2]: Вариадики и std::visit
От: SaZ  
Дата: 28.10.20 21:24
Оценка:
Здравствуйте, Chorkov, Вы писали:

C>...


C>Если визиторы невозможно написать без связывания локальных переменных, предется использовать мене читаемое решение:


C>
C>// from https://en.cppreference.com/w/cpp/utility/variant/visit
C>template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
C>template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

C>template<typename ...Args>
C>std::shared_ptr<IFace> factory(const Var& v)
C>{
C>    ....
C>    std::visit( overloaded{
C>         [&p]( typename Args::Key key ) 
C>         { 
C>            p = std::make_shared<Args>(key);
C>         } ...
C>    }, v); 
C>    ...
C>}
C>


Благодарю. Такое же решение мне предложили на stackoverflow. Осталось адаптировать его для своей задачи, что тоже несколько нетривиально.
Мне просто достаточно редко доходилось реализовывать на практике вариадики, я пока ещё в них плаваю.

P.S. пытаюсь поставить оценку за сообщение, но почему-то она исчезает после обновления страницы.
Отредактировано 28.10.2020 21:33 SaZ . Предыдущая версия . Еще …
Отредактировано 28.10.2020 21:26 SaZ . Предыдущая версия .
Re[3]: Вариадики и std::visit
От: johny5 Новая Зеландия
Дата: 29.10.20 09:39
Оценка: +1
C>>
C>>// from https://en.cppreference.com/w/cpp/utility/variant/visit
C>>template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
C>>template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

C>>template<typename ...Args>
C>>std::shared_ptr<IFace> factory(const Var& v)
C>>{
C>>    ....
C>>    std::visit( overloaded{
C>>         [&p]( typename Args::Key key ) 
C>>         { 
C>>            p = std::make_shared<Args>(key);
C>>         } ...
C>>    }, v); 
C>>    ...
C>>}
C>>


Хм, кто сможет объяснить как это компилируется? В частности волнуют записи overloaded(Ts...) и overloaded{
Re[4]: Вариадики и std::visit
От: SaZ  
Дата: 29.10.20 13:30
Оценка:
Здравствуйте, johny5, Вы писали:


C>>>
C>>>// from https://en.cppreference.com/w/cpp/utility/variant/visit
C>>>template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
C>>>template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

C>>>template<typename ...Args>
C>>>std::shared_ptr<IFace> factory(const Var& v)
C>>>{
C>>>    ....
C>>>    std::visit( overloaded{
C>>>         [&p]( typename Args::Key key ) 
C>>>         { 
C>>>            p = std::make_shared<Args>(key);
C>>>         } ...
C>>>    }, v); 
C>>>    ...
C>>>}
C>>>


J>Хм, кто сможет объяснить как это компилируется? В частности волнуют записи overloaded(Ts...) и overloaded{


Я сначала тоже не вдуплял
Посмотрите пример по ссылке — https://en.cppreference.com/w/cpp/utility/variant/visit.
У меня тоже самое, только на вариадиках.
    for (auto& v: vec) {
        // 4. another type-matching visitor: a class with 3 overloaded operator()'s
        std::visit(overloaded {
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
        }, v);
    }


Это всё раскрывается в такой псевдокод, через перегрузку оператора() и вариадики:
struct overloaded
{
  operator()(auto arg)
  {
    [](auto arg) { std::cout << arg << ' '; }(arg);
  }
  operator()(double arg)
  {
    [](double arg) { std::cout << std::fixed << arg << ' '; }(arg);
  }
  operator()(const std::string& arg)
  {
    [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }(arg);
  }
}


std::visit просто не умеет принимать на вход массив лямбд, вот они и заворачивается в функтор с перегруженными скобочками, который уже понятен для std::visit.
Отредактировано 29.10.2020 14:14 SaZ . Предыдущая версия . Еще …
Отредактировано 29.10.2020 13:32 SaZ . Предыдущая версия .
Отредактировано 29.10.2020 13:31 SaZ . Предыдущая версия .
Re[4]: Вариадики и std::visit
От: Chorkov Россия  
Дата: 29.10.20 15:21
Оценка: 9 (3)
Здравствуйте, johny5, Вы писали:


C>>>
C>>>// from https://en.cppreference.com/w/cpp/utility/variant/visit
C>>>template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
C>>>template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

C>>>template<typename ...Args>
C>>>std::shared_ptr<IFace> factory(const Var& v)
C>>>{
C>>>    ....
C>>>    std::visit( overloaded{
C>>>         [&p]( typename Args::Key key ) 
C>>>         { 
C>>>            p = std::make_shared<Args>(key);
C>>>         } ...
C>>>    }, v); 
C>>>    ...
C>>>}
C>>>


J>Хм, кто сможет объяснить как это компилируется? В частности волнуют записи overloaded(Ts...) и overloaded{


template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };

Мы объявили шаблонный overloaded класс, унаследованный от всех аргументов шаблона.
using Ts::operator()... мы вынесли определения операторов () из базовых классов.
Теперь все этим операторы равнозначны.
У класса есть конструктор по умолчанию, который можно было бы использовать так:
overloaded<A,B,C>  visitor{ A(1,2,3), B(3,4,5), C(6,7,8) };

или, если нужна временная переменная для использования в качестве аргумента функции:
foo( overloaded<A,B,C>{ A(1,2,3), B(3,4,5), C(6,7,8) } );

Однако такое использование, в случае лямбд, неудобно — мы не можем явно указать их типы.
Ранее эта проблема решалась написанием специальной функции make***, которая на основе типов своих аргументов
выводила бы тип результата. Примерно так:
template<typename...Ts>
overloaded<Ts...> make_overloaded( Ts ... args )
{
    return overloaded<Ts...>{ args... };
}
foo( make_overloaded( A(1,2,3), B(3,4,5), C(6,7,8) ) );


Однако, в С++17 появился вывод параметров шаблона на основании типов аргументов конструктора (Class Template Argument Deduction).
Эта возможность призвана избавить нас от всяческих make_tuple, make_unique и т.д.
В частности, строчка
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

говорит, что если вызван конструктор переменной (в т.ч. временной), для шаблона overloaded, без указания параметров шаблона, то надо вызвать конструктор для класса параметризованного для типами, из аргументов конструктора.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.