Justin
Justin

Reputation: 25287

Why does this code involving use of references to temporaries segfault although it appears to properly manage lifetimes?

While experimenting with move-free and copy-free code, I wrote the following:

#include <functional>
#include <type_traits>
#include <utility>

#define FWD(...) ::std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)

namespace {
    template <typename Fn>
    class wrapped_fn
    {
    public:
        template <typename F>
        explicit wrapped_fn(F&& fn)
            : fn_{FWD(fn)}
        {}

        auto foo() &&
        {
            return wrapped_fn<Fn>{
                FWD(fn_),
            };
        }

        auto trigger_segfault() &&
        {
            return FWD(fn_)();
        }

    private:
        Fn&& fn_;
    };

    template <typename F>
    auto wrap(F&& f)
    {
        return ::wrapped_fn<F>{
            FWD(f),
        };
    }

    template <typename F>
    auto frobnicate(F&& callable)
    {
        return ::wrap([&callable] {
            return callable(); //
        });
    }

    std::function<int()> call_me()
    {
        return [] { return 42; };
    }
}

int main()
{
    return ::frobnicate(call_me())
        .foo()
        .trigger_segfault();
}

I expected this code to compile and work fine (have a return code of 42). Since I am simply maintaining a reference to the function until the call to .trigger_function() and temporaries bound to references are alive for the full expression (until the ;), I should not have any dangling references or copies/moves.

So, why does this segfault when compiled with either gcc or MSVC?


Through use of gdb, I determined that the call to wrapped_fn's constructor in the .foo() member function is where the symptoms of the problem start to show. At the beginning of the constructor call, we have:

(gdb) p fn
$10 = ((anonymous namespace)::<lambda()> &&) @0x7ffffffecdd0: {__callable = @0x7ffffffeced0}
(gdb) p fn.__callable
$11 = (std::function<int()> &) @0x7ffffffeced0: {<std::_Maybe_unary_or_binary_function<int>> = {<No data fields>}, <std::_Function_base> = {static _M_max_size = 16,
    static _M_max_align = 8, _M_functor = {_M_unused = {_M_object = 0x0, _M_const_object = 0x0, _M_function_pointer = 0x0, _M_member_pointer = NULL},
      _M_pod_data = '\000' <repeats 15 times>},
    _M_manager = 0x4ba0d2 <std::_Function_base::_Base_manager<(anonymous namespace)::call_me()::<lambda()> >::_M_manager(std::_Any_data &, const std::_Any_data &, std::
_Manager_operation)>}, _M_invoker = 0x4ba0b0 <std::_Function_handler<int(), (anonymous namespace)::call_me()::<lambda()> >::_M_invoke(const std::_Any_data &)>}

After the initialization of the fn_ member, we have:

(gdb) p fn
$12 = ((anonymous namespace)::<lambda()> &&) @0x7ffffffecdd0: {__callable = @0x7ffffffecdd0}
(gdb) p fn.__callable
$13 = (std::function<int()> &) @0x7ffffffecdd0: {<std::_Maybe_unary_or_binary_function<int>> = {<No data fields>}, <std::_Function_base> = {static _M_max_size = 16,
    static _M_max_align = 8, _M_functor = {_M_unused = {_M_object = 0x7ffffffecdd0, _M_const_object = 0x7ffffffecdd0, _M_function_pointer = 0x7ffffffecdd0,
        _M_member_pointer = (void (std::_Undefined_class::*)(std::_Undefined_class * const)) 0x7ffffffecdd0, this adjustment -8574455321466846208},
      _M_pod_data = "<garbage data>"}, _M_manager = 0x7ffffffecf10}, _M_invoker = 0x4b9be6 <main()+83>}

The __callable member changed. I do not understand why, because both fn_ and fn are references, and FWD(fn) should just be a cast preserving the category of fn. fn is definitely not getting copied or moved, since I captured a variable which counts calls to the special member functions and the counts are not incremented.

Upvotes: 2

Views: 174

Answers (1)

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153810

The lambda function inside frobnicate() lives until frobnicate() returned. It is only referenced and binding things to a reference does not, in general, keep objects alive. That is, the object returned from frobnicate() references a destroyed object, touching it results in undefined behavior.

The case where a reference keeps an object alive is when binding a temporary immediately to a local reference. Even that assumes that the temporary isn’t hidden in any form. For example, it doesn’t work when wrapping the temporary into any form of call.

Upvotes: 7

Related Questions