Search code examples
c++c++17variadic-templatestemplate-meta-programmingvariant

How to sum over an array of variant using a visitor?


I'm trying to find a way to sum over an std::array of std::variant using a visitor. I've gotten this far, but I can't for the life of me figure out how to deduce the type of the visitors without including a void entry at the head of my visitor lambda list.

Does anyone know of a way I can deduce the return type of the lambdas in the visitor so that I don't have to rely on this?

Here's what I've got right now:

#include <array>
#include <iostream>
#include <string_view>
#include <type_traits>
#include <variant>

using namespace std::literals::string_view_literals;

template<typename... Base>
struct Visitor: Base ... {
    using Base::operator()...;
};

template<typename... T>
Visitor(T...) -> Visitor<T...>;

// There has to be a better way to deduce Result than what I'm doing...
template<typename... T, typename S, typename... Ss, size_t N, typename Result = typename std::result_of_t<S()>>
constexpr std::enable_if_t<std::is_arithmetic_v<Result>, Result>
summation(const Visitor<S, Ss...> &visitor, const std::array<std::variant<T...>, N> &array) {
    Result sum{};
    for (const auto &a: array)
        sum += std::visit(visitor, a);
    return sum;
}

int main() {
    constexpr Visitor visitor {
            // This first entry should be unnecessary, I would think:
            []()                   -> double { return 0; },
            [](double d)           -> double { return d + 3.4; },
            [](int i)              -> double { return i - 2; },
            [](std::string_view s) -> double { return s.size(); }
    };

    constexpr std::array<std::variant<int, double, std::string_view>, 5> arr{9.0, 9, 3, 5.2, "hello world"sv};
    constexpr auto val = summation(visitor, arr);
    std::cout << val << '\n';
}

Edit: I'd like the result to be constexpr.

Thanks for any help.


Solution

  • A simplification (I hope) of the Frank's decltype()/std::declval() solution.

    Using decltype()/std::declval(), you don't need to know S, Ss... and T...; you simply need a template type V for visitor and a template type for array.

    You can also avoid the static_assert(), if you prefer, re-enabling SFINAE simply writing

    template <typename V, typename A,
              typename R = decltype(std::visit(std::declval<V>(), std::declval<A>().at(0)))>
    constexpr std::enable_if_t<std::is_arithmetic_v<R>, R>
       summation(V const & visitor, A const &array)
    {
        R sum{};
        for (const auto &a: array)
            sum += std::visit(visitor, a);
        return sum;
    }