v.p
v.p

Reputation: 71

Temporary inside a visit() call?

I have a compilation warning about returning a reference to a local object while using visit(), but I fail to understand why...

I use the following code (it's a bit contrived, but this is for demonstration purposes):

#include <iostream>

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

template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;


using namespace std;


class B {
public:
  B(int i) :
    i_(i) {}

  int get_i() const {
    return i_;
  }

private:
  int i_;
};

class D: public B {
public:
  D(int i) :
    B(i) {}
};

const B& as_base(const variant<B, D>& op) {
    return visit(overloaded {
        [](const B& b) { return static_cast<const B&>(b); },
        [](const D& d) {
            cout << "in as_base: &d = " << &d << "\n";
            const B& b = static_cast<const B&>(d);
            cout << "in as_base: &b = " << &b << "\n";
            return b;
        }}, op);
}

auto as_base_lambda = [](const variant<B, D>& op) {
    return visit(overloaded {
        [](const B& b) { return static_cast<const B&>(b); },
        [](const D& d) {
            cout << "in as_base_lambda: &d = " << &d << "\n";
            const B& b = static_cast<const B&>(d);
            cout << "in as_base_lambda: &b = " << &b << "\n";
            return b;
        }}, op);
};

int main() {
  variant<B, D> v(d);
  const B& b1 = get<1>(v);
  const B& b2 = as_base(v);
  const B& b3 = as_base_lambda(v);
  cout << "&b1 = " << &b1 << " - i = " << b1.get_i() << "\n";
  cout << "&b2 = " << &b2 << " - i = " << b2.get_i() << "\n";
  cout << "&b3 = " << &b3 << " - i = " << b3.get_i() << "\n";
}

When I compile (clang++ 17, gcc++ 13), I get this warning:

test.cpp:33:12: warning: returning reference to local temporary object [-Wreturn-stack-address]
   33 |     return visit(overloaded {
      |            ^~~~~~~~~~~~~~~~~~
   34 |             [](const B& b) { return static_cast<const B&>(b); },
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   35 |             [](const D& d) {
      |             ~~~~~~~~~~~~~~~~
   36 |                 cout << "in as_base: &d = " << &d << "\n";
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   37 |                 const B& b = static_cast<const B&>(d);
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   38 |                 cout << "in as_base: &b = " << &b << "\n";
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   39 |                 return b;
      |                 ~~~~~~~~~
   40 |             },
      |             ~~
   41 |                 }, op);

So the as_base() function apparently creates some temporary and returns a reference to it. But where? what temporary are we talking about? What I don't understand is that the seemingly equivalent function as_base_lambda() has no problem.

If I run the code nonetheless, I get:

# here is the address of the d object in the variant
&b1 = 0x7fff02fb55f8 - i = 5

# the visitors in as_base() and as_base_lambda() receive the same object (same address);
# note also that the b object has the same address as d, as expected:
in as_base: &d = 0x7fff02fb55f8
in as_base: &b = 0x7fff02fb55f8
in as_base_lambda: &d = 0x7fff02fb55f8
in as_base_lambda: &b = 0x7fff02fb55f8

# however the final object returned by as_base() is different, and apparently
# plain wrong (the value i is off):
&b2 = 0x7fff02fb5594 - i = 21880

# the final object returned by as_base_lambda() is yet again different, but its value
# is correct...
&b3 = 0x7fff02fb55d4 - i = 5

I have trouble understanding what's going on here: is it visit() that creates a temporary, and eventually returns its address? but why is as_base_lambda() legitimate, and not as_base()?

Interestingly, if I modify as_lambda() to return a pointer instead:

const B* as_base(const variant<B, D>& op) {
    return visit(overloaded {
            [](const B& b) { return static_cast<const B*>(&b); },
            [](const D& d) { return static_cast<const B*>(&b); }
            }}, op);
}

Then I have no warning, and it "works"...

Upvotes: 1

Views: 68

Answers (1)

Ted Lyngmo
Ted Lyngmo

Reputation: 117937

Your lambas will return B unless you specify the type to be const B&. Lambdas are implicitly auto and auto is never a reference unless you explicitly specify it with auto&.

const B& as_base(const variant<B, D>& op) {
    return visit(overloaded{[](const B& b) -> const B& { return b; }, // or auto&
                            [](const D& d) -> const B& { return d; }},
                 op);
}

if I modify as_lambda() to return a pointer instead [...] it "works"

Yes, a plain auto can be deduced to be of pointer type - but not of reference type - unless you use auto&/auto&& or decltype(auto) (since C++14).

Upvotes: 5

Related Questions