Home > Back-end >  Function accepting a reference to std::variant
Function accepting a reference to std::variant

Time:07-22

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
}

Complete Example

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;
}

Demo

  • Related