Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple std::variant visit with variadic templated helper

I am attempting to make a function that helps handling N std::variant types.

Note: I am trying to make all pathways compile time validated. So std::optional and std::holds_alternative are not viable for me.

The implementation is as follows:

template<typename T>
using Possible = std::variant<std::monostate, T>;

template<typename... Types>
void ifAll(std::function<void(Types...)> all, Possible<Types>&&... possibles)
{
    std::visit(
        [&](auto&&... args) {
            if constexpr ((... &&
                           std::is_same_v<std::decay_t<decltype(args)>, Types>))
            {
                return all(std::forward<Types>(args)...);
            }
            else
            {
                std::cout << "At least one type is monostate" << std::endl;
            }
        },
        possibles...);
}

And an example of using the function is:

int main()
{
    Possible<int>  a = 16;
    Possible<bool> b = true;

    ifAll([](const int& x, const bool& y)
              -> void { std::cout << "All types set!" << std::endl; },
          a,
          b);
}

However I get a compiler error:

TestFile.cc: error: no matching function for call to 'ifAll'
    ifAll([](const int& x, const bool& y)
    ^~~~~

TestFile.cc: note: candidate template ignored: could not match
    'function<void (type-parameter-0-0...)>' against '(lambda at
    TestFile.cc)'

void ifAll(std::function<void(Types...)> all, Possible<Types>&&... possibles)
    ^

Why does the lambda I provide not match the function signature?

Attempted Fix 1

I tried moving in a and b which still does not work:

ifAll([](const int& x, const bool& y)
              -> void { std::cout << "All types set!" << std::endl; },
          std::move(a),
          std::move(b));
like image 977
Hurricane Development Avatar asked Apr 06 '20 05:04

Hurricane Development


2 Answers

Following call would work:

int main() {
    Possible<int>  a = 16;
    Possible<bool> b = true;

    std::function<void(int, bool)> fun = [](int x, bool y) -> void {
        std::cout << "All types set!" << std::endl;
    };

    ifAll(fun,
          std::move(a),
          std::move(b));
}

or switch your function signature to:

template <typename... Types>
void ifAll(std::function<void(Types...)> const& all, Possible<Types>&... possibles)

and then you can call it without std::move:

int main() {
    Possible<int>  a = 16;
    Possible<bool> b = true;

    std::function<void(int, bool)> fun = [](int x, bool y) -> void {
        std::cout << "All types set!" << std::endl;
    };

    ifAll(fun, a, b);
}
like image 103
NutCracker Avatar answered Oct 18 '22 12:10

NutCracker


An easy solution is to use a function object + std::optional:

#include <functional>
#include <optional>

struct Error {};

template <typename F, typename... Args>
decltype(auto) if_all(F&& f, Args&&... args)
{
    if ((args && ...)) {
        return std::invoke(std::forward<F>(f), *std::forward<Args>(args)...);
    } else {
        throw Error{};
    }
}

Usage example:

#include <functional>
#include <iostream>

int main()
{
    std::optional<int> a{5};
    std::optional<int> b{10};
    std::cout << if_all(std::plus{}, a, b) << '\n';
}

(live demo)

If you insist to use std::variant instead of std::optional (which is probably because of some misunderstandings about either of them), the idea is the same — you need to check if all arguments are "empty" first (maybe using std::holds_alternative), and unwrap the arguments after.

like image 33
L. F. Avatar answered Oct 18 '22 12:10

L. F.