捕获对对象的引用会导致分段错误

Capturing a reference to object causes segmentation fault

当通过引用或复制调用捕获引用时,对 name() 的调用会导致分段错误。 使用 std::bind 没有问题。为什么?

struct with_name
{
    template<typename T>
    explicit with_name(T& t)
            : // name([&]{return t.name();})
            name(std::bind(&T::name, &t))
    {}
    std::function<const std::string&()> name;
};

struct Person
{
    const std::string& name() const
    {
        return name_;
    }
    std::string name_ = "Jon";
};

int main()
{
    Person person{};

    with_name withName{person};

    // segmentation fault
    std::cout << withName.name() << std::endl;
    return 0;
}

来自 cppreference:

备注 当结果类型为引用的 std::function 是从没有尾随 return 类型的 lambda 表达式初始化时,应小心。由于自动推导的工作方式,这样的 lambda 表达式将始终 return 一个纯右值。因此,生成的引用通常会绑定到一个临时的,其生命周期在 std::function::operator() returns.

时结束
std::function<const int&()> F([]{ return 42; });
int x = F(); // Undefined behavior: the result of F() is a dangling reference

同样来自 cppreference:

template< class F >
function( F f );
  1. 用std::move(f)初始化目标。如果 f 是指向函数的空指针或指向成员的空指针,则 *this 在调用后将为空。此构造函数不参与重载决策,除非 f 对于参数类型 Args... 和 return 类型 R.
  2. 是 Callable

那么为什么当你在外部传递一个可调用对象时它不起作用,但是当你在参数范围内创建它时它是 UB?不是可以复制吗?

问题不是捕获引用,而是 lambda 的 return 类型与 std::function 的签名不匹配。给定 [&]{return t.name();},return 类型将是 std::string,但 name 期望 returning const std::string&.

将 lambda 更改为

template<typename T>
explicit with_name(T& t)
        :  name([&] () -> decltype(auto) {return t.name();})
        //                ^^^^^^^^^^^^^^
        //                deduce return type as const string& when `T` is Person
{}