#Doing a different operation based on what is inside a variant

26 messages · Page 1 of 1 (latest)

modest locust
#

I’ve got a std::variant and I want to do a binary operation (say + for example) based on the types stored in the LHS and RHS of operation (which is a variant). Eg if the LHS and RHS were strings, we’d add them, and if they were ints we’d add them, but if we got a bool and an int we’d error.

Is there a really nice way to write this? I’m aware of std::visit but attempting to use it is leading to masses of over complex overloaded functions.

In an ideal world (this is my Python brain speaking) I’d like to write something like

If (LHS == int and RHS == int) then {do this}

So on

tidal hillBOT
#

When your question is answered use !solved to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

modest locust
#

If there’s not an elegant way to do this with variants how could I restructure to be able to handle this in a really simple way?

#

Also to clarify, this isn’t done until runtime

balmy charm
#
template <class... Ts>
struct overload : Ts...
{
    using Ts::operator()...;
};

// deduction guide that was necessary for clang at the time
template <class... Ts>
overload(Ts...) -> overload<Ts...>;

// Example usage from a random project I found:
State transition(state::Valid, const Token &token)
{
    // std::cout << "Transition: Valid" << std::endl;
    return std::visit(overload{[](token::Semicolon) -> State
                               { return state::Valid{}; },
                               [](auto) -> State
                               { return state::Invalid{}; }},
                      token.value());
}
flint rapids
#

;compile -std=c++23

#include <memory>
#include <algorithm>
#include <iostream>
#include <vector>
#include <variant>

template <typename... TVisitorFunction>
struct variant_visitor : TVisitorFunction... {
    template <typename... TCallable>
    variant_visitor(TCallable&&... vc)
        : TVisitorFunction{ std::forward<TCallable>(vc) }...
    { }
    using TVisitorFunction::operator()...;
};

template <typename... TVisitorFunction>
variant_visitor(TVisitorFunction...)
    ->  variant_visitor<std::remove_reference_t<TVisitorFunction>...>;

struct A { int a{}; };
struct B { int b{}; };
struct C { int c{}; };
struct D { int d{}; };

int main() {
    std::vector<std::variant<A, B, C, D>> variants = {
        A{ .a = 123 },
        B{ .b = 234 },
        D{ .d = 987 },
    };
    
    for (auto& var : variants) {
        std::visit(variant_visitor{
            [](A& a_variant) {
                std::cout << "A: " << a_variant.a << std::endl;
            },
            [](B& b_variant) {
                std::cout << "B: " << b_variant.b << std::endl;
            },
            [](auto& other) {
                if constexpr (std::same_as<std::remove_cvref_t<decltype(other)>, C>)
                    std::cout << "C: " << other.c << std::endl;
                if constexpr (std::same_as<std::remove_cvref_t<decltype(other)>, D>)
                    std::cout << "D: " << other.d << std::endl;
            },
        }, var);
    }
}
thorny echoBOT
#
Program Output
A: 123
B: 234
D: 987
flint rapids
#

another example ^, same concept as what java Monke posted

heavy mountain
#

"If (LHS == int and RHS == int) then {do this}" is

if (std::holds_alternative<int>(LHS) and std::holds_alternative<int>(RHS)) {
  //do this
}
flint rapids
#

TIL std::holds_alternative<> exists haha. seems like that'll come in handy in the future 👍

balmy charm
# heavy mountain "If (LHS == int and RHS == int) then {do this}" is ```cpp if (std::holds_alterna...

@modest locust
You can obviously use that approach and wrap it in a function like e.g. so:

template <typename T>
bool both_variants_hold(const auto& a, const auto& b) {
    return std::holds_alternative<T>(a) && std::holds_alternative<T>(b);
}
```which you could e.g. call like so:
```cpp
std::variant<int, float> x = float{1};
std::variant<int> y = int{2};

if (both_variants_hold<int>(x, y)) {
    std::cout << "Success\n";
} else {
    std::cout << "Failed\n";
}

I’m aware of std::visit but attempting to use it is leading to masses of over complex overloaded functions.
Depending on how much functionality you're trying to provide (and how much that differs from C++ regular overloads), there may not be a way around masses of functions.
There's almost always a way to simplify "over complex" functions though.

flint rapids
#

it might be possible to keep generic using templates and working with a tuple/pair of the variants

vale ore
#

also prolly need to mention that std::visit can take a visitor and apply it to multiple variants

runic holly
runic holly
#

;compile ```cpp
#include <stdexcept>
#include <string>
#include <variant>
#include <iostream>

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

template <typename ...Ts>
overloaded(Ts...) -> overloaded<Ts...>;

using V = std::variant<int, std::string>;

V
add(V const& x, V const& y) {
return std::visit(overloaded {
[](int const& x, int const& y) -> V { return x + y; },
[](std::string const& x, std::string const& y) -> V { return x + y; },
[](auto const&, auto const&) -> V { throw std::runtime_error{"error"}; }
}, x, y);
}

int main() {
V x{1};
V y{2};

std::cout << std::get<int>(add(x, y)) << '\n';

x = "x";
y = "y";

std::cout << std::get<std::string>(add(x, y)) << '\n';

try {
    x = 1;
    std::cout << std::get<std::string>(add(x, y)) << '\n';
} catch (std::exception &ex) {
    std::cout << ex.what() << '\n';
}

}

thorny echoBOT
#
Program Output
3
xy
error
slow quail
half matrix
#

you also can just make lambda with constexpr if

#
            resultValue = co_await tcps_WaitDisconnect(client);
       int result = std::visit([](auto &&arg)constexpr -> int
            {
                using T = std::decay_t<decltype(arg)>;

                if constexpr(std::is_same_v<T, bool>) {  return arg ? 0 : 1;  }
                else if constexpr(std::is_same_v<T, std::exception_ptr>)
                {
                    try {  std::rethrow_exception(arg);  }
                    catch (const std::exception &e) {
                        std::cerr << "Caught exception: " << e.what() << std::endl;
                        qDebug() << "Caught exception: " << e.what();
                    }
                    return -1;
                }
                qDebug() << "result == monostate in server doWork";
                return -1; // ? socket not open
            }, resultValue);```
modest locust
#

I swear the standard library is like this bottomless toolbox that has everything I need if only I knew where to look 🙂

modest locust
#

Thank you very much

#

!solved