kanoisa
kanoisa

Reputation: 420

How to return const& from std::visit?

I have encountered what i consider to be a strange situation with std::visit and overloaded which is giving me a compiler error. To illustrate what i am trying to do I have an example using std::visit 3 different ways.

My goal is to be able to wrap the std::visit part in a function because in my use case it's not a small piece of code. I have multiple lambda overloads. I don't want to have to duplicate this code in each function that uses the variant and wants to get a property, i want to write it once in some kind of wrapper which i can re-use across multiple translation units (Approach 3 is most preferable to me, then Apporach 2, and all else fails, I will investigate something less pleasing like a macro or something.)

Can someone explain to me:

Please consider this minimal example which reproduces the problem I am seeing.

Minimal example:

#include <iostream>
#include <string>
#include <variant>

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

struct a {
    std::string name;

    std::string const& get_name() const { return name; }
};

struct b {
    std::string name;

    std::string const& get_name() const { return name; }
};

std::string const& visit_get_name(std::variant<a, b> const& data) {
    return std::visit(overloaded{[](auto const& o){return o.get_name();}}, data);
}

struct wrapper {

    std::string const& get_name() const {
        return std::visit(overloaded{[](auto const& o){return o.get_name();}}, data);
    }

    std::variant<a, b> data;
};

int main(int, char**) {
    std::variant<a, b> a_thing{a{"sue"}};
    wrapper a_wrapper{a_thing};

    std::cout << "Approach 1: " << std::visit(overloaded{[](auto const& o){return o.get_name();}}, a_thing);
    std::cout << "Approach 2: " << visit_get_name(a_thing);
    std::cout << "Approach 3: " << a_wrapper.get_name();

    return 0;
}

Compiler:

GCC 11.2.0

Compiler errors:

example.cpp: In function 'const string& visit_get_name(const std::variant<a, b>&)':
example.cpp:20:22: error: returning reference to temporary [-Werror=return-local-addr]
   20 |     return std::visit(overloaded{[](auto const& o){return o.get_name();}}, data);
      |            ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
example.cpp: In member function 'const string& wrapper::get_name() const':
example.cpp:26:26: error: returning reference to temporary [-Werror=return-local-addr]
   26 |         return std::visit(overloaded{[](auto const& o){return o.get_name();}}, data);
      |                ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Compiler switches:

-Wall 
-Wextra 
-Wshadow 
-Wnon-virtual-dtor 
-Wold-style-cast 
-Wcast-align 
-Wunused 
-Woverloaded-virtual 
-Wpedantic 
-Wconversion 
-Wsign-conversion 
-Wnull-dereference 
-Wdouble-promotion 
-Wformat=2 
-Werror 
-Wmisleading-indentation 
-Wduplicated-cond 
-Wduplicated-branches 
-Wlogical-op 
-std=gnu++20

Upvotes: 4

Views: 1113

Answers (1)

cigien
cigien

Reputation: 60218

The default behavior when calling a lambda (or a function for that matter) is to have the value returned by copy. Since your lambda expressions that you pass to overloaded return by copy, binding a reference to that in the return type of visit_get_name (or wrapper::get_name) is not allowed, which is why Approach 2 and 3 fail.

If you want to return by reference, you have to say so (note the explicit -> auto const & trailing return type for the lambda), like this:

return std::visit(overloaded{[](auto const& o) -> auto const & {
                               return o.get_name();
                             }}, data);

(and the same thing for wrapper::get_name as well).

This isn't needed for Approach 1, since you're not binding a reference to what's returned.

demo.

Upvotes: 8

Related Questions