Reputation: 361
I'm trying to use std::variant as a class member variable and then use operator overloading so that the two Variants
of this class can use the operator plus
to produce a new variable. The problem is that std::get does not work as I thought and so I cannot retrieve the correct (hardcoded) string types so that the AddVisitor
struct is used.
I get a compilation error that says: no matching function for call to ‘get<0>(std::basic_string&)’
Also is there a way that operator+
function detects the type without if-else
statements?
I have already checked a lot of answers in SO including ones that answer questions about similar Boost functionality, but I cannot get it to work.
#include <iostream>
#include <variant>
#include <string>
#include "stdafx.h"
using Variant = std::variant<int, std::string>;
template<typename T>
struct AddVisitor
{
T operator()(T v1, T v2)
{
return v1 + v2;
}
};
class Var
{
Variant v;
public:
template<typename T>
Var(T value) : v(value) {}
Var operator+(Var& val)
{
// PROBLEM: This is a hard coded example that I want to use, so that concatenation of two strings happens.
return std::visit(AddVisitor<std::string>(), std::get<std::string>(v), std::get<std::string>(val.get()));
// Is there a way to get the correct type without if-else statements here?
}
Variant get()
{
return v;
}
};
int main()
{
Var x("Hello "), y("World");
// The expected output is this:
Var res = x + y;
return 0;
}
I expect to be able to use the plus operator and concatenate two strings or two integers and create a new Var
variable.
Upvotes: 2
Views: 3012
Reputation: 75815
Ok, so there are a few things to talk about.
First, the visitor for std::visit
with more than one variant argument should accept all combinations of variant types. In your case it should accept:
(string, string)
(string, int)
(int, int)
(int, string)
If for you only string, string
and int, int
are valid you still need to accept the other combinations for the code to compile, but you can throw in them.
Next, the visitor shouldn't be templated. Instead the operator()
should be templated or overloaded for all the above combinations.
So here is AddVisitor
:
struct AddVisitor
{
auto operator()(const std::string& a, const std::string& b) const -> Variant
{
return a + b;
}
auto operator()(int a, int b) const -> Variant
{
return a + b;
}
// all other overloads invalid
template <class T, class U>
auto operator()(T, U) const -> Variant
{
throw std::invalid_argument{"invalid"};
}
};
It's not clear from documentation what the overloads can return, but I couldn't make it compile unless all return Variant
. Fortunately the compiler errors are TREMENDOUSLY HELPFULL . (I need to check the standard).
Next, when you call std::visit
you need to pass the variant
s you have.
So the final code is this:
auto operator+(Var& val) -> Var
{
return std::visit(AddVisitor{}, get(), val.get());
}
And you can indeed use it like you want:
Var res = x + y;
Another issue with your code is that get
makes unnecessary copies. And copies of std::variant
are not cheap to make. So I suggest:
auto get() const -> const Variant& { return v; }
auto get() -> Variant& { return v; }
Upvotes: 5