Reputation: 420
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.
std::visit
directly on a variant which returns a T const&
,std::visit
code in a free function: T const& fn(variantT const&)
T const& get_XXX() const
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:
std::visit
is creating a temporary object?Please consider this minimal example which reproduces the problem I am seeing.
#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;
}
GCC 11.2.0
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);
| ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-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
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