I'm trying to pass values to a function accepting a std::variant
.
I noticed I can use a function accepting a const reference to a variant value, but not a reference alone. Consider this code
#include <variant>
#include <queue>
#include <iostream>
struct Foo{ std::string msg{"foo"}; };
struct Bar{ std::string msg{"bar"}; };
using FooBar = std::variant<Foo,Bar>;
void f1(const FooBar&)
{
std::cout << "yay" << std::endl;
}
void f2(FooBar&)
{
std::cout << "wow" << std::endl;
}
int main()
{
Foo f;
Bar b;
f1(f); // fine
f1(b); // fine
f2(f); // compile error
}
gives me error
invalid initialization of reference of type 'FooBar&' {aka 'std::variant<Foo, Bar>&'} from expression of type 'Foo'
42 | f2(f);
so the first question is: why is that prohibited? I can't figure out.
Why I'm doing this?
I'm trying to use two accessor function to read and modify the values using std::visit
, something like this:
#include <variant>
#include <queue>
#include <iostream>
struct Foo{ std::string msg{"foo"}; };
struct Bar{ std::string msg{"bar"}; };
using FooBar = std::variant<Foo,Bar>;
std::string f3(const FooBar& fb)
{
return std::visit([](auto& foobar){
std::string ret = "yay ";
return ret foobar.msg;
}, fb);
}
void f4(FooBar& fb)
{
std::visit([](auto& foobar){
foobar.msg = "doo";
}, fb);
}
int main()
{
Foo f;
Bar b;
std:: cout << f3(f) << " " << f3(b); // fine
f4(f); // does not compile
}
which of course does not compile with
error: cannot bind non-const lvalue reference of type 'FooBar&' {aka 'std::variant<Foo, Bar>&'} to an rvalue of type 'FooBar' {aka 'std::variant<Foo, Bar>'}
44 | f4(f);
| ^
So second question: how can I achieve this behaviour?
CodePudding user response:
You cannot pass a temporary resulting from converting the Foo
to a std::variant<Foo,Bar>
to f2
. C disallows this because binding a temporary to a non-const reference is most likely a bug. If it was possible you'd have no way to inspect the modified value anyhow.
You can workaround this by using a std::variant
of references (actually std::reference_wrapper
) and pass that by value. You'd then use two overloads, one for non-const and one for const references, in both cases the temporary std::variant
can be passed by value while modifications inside the function are made directly on the object used to construct the std::variant
:
#include <variant>
#include <queue>
#include <iostream>
#include <functional>
struct Foo{ std::string msg{"foo"}; };
struct Bar{ std::string msg{"bar"}; };
using FooBar = std::variant<std::reference_wrapper<Foo>,std::reference_wrapper<Bar>>;
using ConstFoobar = std::variant<std::reference_wrapper<const Foo>,std::reference_wrapper<const Bar>>;
void f1(ConstFoobar)
{
std::cout << "yay" << std::endl;
}
void f2(FooBar)
{
std::cout << "wow" << std::endl;
}
int main()
{
Foo f;
Bar b;
f1(f); // yay
f1(b); // yay
f2(f); // wow
}
PS: The simpler solution is of course to have two overloads taking plain Foo
and Bar
(ie for each a non and a const reference overload, makes 4 in total). Though I suppose you are using std::variant
for some other reasons not directly apparent from the example code.
CodePudding user response:
So second question: how can I achieve this behaviour?
It seems you use std::variant
to restrict possible argument.
If that is the case, template might be an alternative solution:
template <typename T>
constexpr bool isFooBar = std::is_same_v<T, Foo> || std::is_same_v<T, Bar>;
template <typename FooBar, std::enable_if_t<isFooBar<FooBar>, int> = 0>
void f1(const FooBar&)
{
std::cout << "yay" << std::endl;
}
template <typename FooBar, std::enable_if_t<isFooBar<FooBar>, int> = 0>
void f2(FooBar&)
{
std::cout << "wow" << std::endl;
}
template <typename FooBar, std::enable_if_t<isFooBar<FooBar>, int> = 0>
std::string f3(const FooBar& fb)
{
std::string ret = "yay ";
return ret fb.msg;
}
template <typename FooBar, std::enable_if_t<isFooBar<FooBar>, int> = 0>
void f4(FooBar& fb)
{
fb.msg = "doo";
}
int main()
{
Foo f;
Bar b;
f1(f); // yay
f1(b); // yay
f2(f); // wow
std:: cout << f3(f) << " " << f3(b) << std::endl; // yay foo yay bar
f4(f); // f.msg = foodoo
std::cout << f.msg<< std::endl;
}