0

Sorry if this case is rather complicated, but I hope it helps people to better understanding modern c++ usage. So I want to make this code works. It should produce special lambdas for single integral type and for variant type for calculate sequence of terms on hard static cast to single type or on soft variant cast to common type, while this terms will be using. I add comments which describes what I really try to do in this code.

#include <variant>
#include <type_traits>
#include <string>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <any>
#include <utility>

/* Visitor */ 
// Should get values from a variant
template <class... Ts>
struct Visitor;

template <class T, class... Ts>
struct Visitor<T, Ts...> : T, Visitor<Ts...> {
    Visitor(T t, Ts... rest) : T(t), Visitor<Ts...>(rest...) {}
    using T::operator();
    using Visitor<Ts...>::operator();
};

template <class T>
struct Visitor<T> : T {
    Visitor(T t) : T(t) {}
    using T::operator();
};

/* Calculator */
// Should get special lambda for same type integrals and for variant packed integrals
template<class T, class... Ts>
class Calculator {
public:

    // lambda for simple calculate same type integrals
    static auto sum = [](T a, T b) { return a + b; };

    // api for get current value of variant
    template<typename... Vs>
    static auto variant_value(std::variant<Vs...> v) {
        return std::visit(Visitor<Vs...>{}, v);
    }

    // lambda for unpack variant arguments calc common value and pack in variant again
    template<typename... Vs>
    static auto variant_sum = [](std::variant<Vs...> a, std::variant<Vs...> b) {
        auto value = variant_value<Vs...>(a) + variant_value<Vs...>(b);
        return std::variant<Vs...>(value);
    };
    
};

/* Term Producer */
namespace term::legion {

    using std::function, std::any;

    template<typename T>
    function<any(any)> legion(auto& algebra) noexcept { // std::function<T(T,T...)
        function<any(any)> redex = [](std::any a) {return a;};
        // I delete code here because its not important now
       return redex;
    }

    // production lamda for single type values 
    template<typename T>
    std::function<std::any(std::any)> sum(T arg) noexcept {
        return legion<T>(Calculator<T>::sum);
    };

    // production lambda for variant type values
    template<typename ...Vs>
    std::function<std::any(std::any)> sum() noexcept {
        std::cout << "variant sum selected" << std::endl;
        return legion<std::variant<Vs...>>(Calculator<Vs...>::template variant_sum<Vs...>);
    };

}

int main() {
     // term contains lambda for single type  
     auto sm = term::legion::sum<int>();
     // term contains lambda for variant type 
     auto v_sm = term::legion::sum<char, int16_t, double>();
}
max66
  • 65,235
  • 10
  • 71
  • 111

1 Answers1

1

I don't see a way to make your code works...

Anyway, some problems/suggestions

  1. maybe your Visitor works but you can make better using the classic way: variadic inheritance
template <typename ... Ts>
struct Visitor : public Ts...
 { using Ts::operator()...; };

and avoiding specialization and recursion

  1. When you call
auto sm = term::legion::sum<int>();

you have, inside sum() (given that Vs... is int),

Calculator<int>::template variant_sum<int>

that call, inside variant_sum (given that Vs... is int),

variant_value<int>(a) + variant_value<int>(b);

that instantiate, inside variant_value<int>(a) (given that Vs... is int), a

 Visitor<int>

The problem is that Visitor inherit from is template paramenters and a class can't inherit from int.

To make a visitor, you need functions that works with all types of the variant but you have to works a little; for example, you can write a sort of operator-wrapper as follows

template <typename T>
struct foo 
 { void operator() (T const &) { } };

and a visitor as follows

template <typename ... Ts>
struct Visitor : public foo<Ts>...
 { using foo<Ts>::operator()...; };

This way Visitor<int> inherit from foo<int> (not from int) and foo<int> as a operator() accepting an int.

This works as visitor but you ask

variant_value<Vs...>(a) + variant_value<Vs...>(b);

for the contained value.

  1. And here come the big problem: you can't have the value from std::visit.

Or better... from std::variant<int> (a variant with only a type), you can. You can modify foo<T> as follows

template <typename T>
struct foo 
 { T operator() (T const & val) { return val; } };

and (with a couple of little other corrections) this call

auto sm = term::legion::sum<int>();

compile.

But the following sum()

auto v_sm = term::legion::sum<char, int16_t, double>();

because the resulting visitor (Visitor<char, std::int16_t, double>) contains three different operator() functions, returning three different types (char, std::int16_t and double).

But std::visit() must return a single type. Not different types according the type active in the variant.

So I suppose your code has to be completely rethinked.

Other little problems

  1. the following declaration is good in C++20
template<typename T>
function<any(any)> legion(auto& algebra) noexcept

but in C++17 you can't use auto as placeholder for the type of an argument (except for generic lambdas); the correct C++17 way pass through an additional template parameter, so

template <typename T, typename U>
function<std::any(std::any)> legion (U & algebra) noexcept
  1. Add constexpr for sum and variant_sum inside Calculator
static constexpr auto sum = ...

static constexpr auto variant_sum = ...
  1. If Calculator has only public members, you make it a struct.
max66
  • 65,235
  • 10
  • 71
  • 111
  • Thank you I I figured out several aspects, it remains unclear for me whether I can pass a reference to a static template function or whether I should design everything in such a way as to call a static function that will produce a new lambda every time. – Sergey Inozemcev Apr 07 '21 at 16:40