Ошибка сегментации с помощью параметров std:: function и лямбда

Не могли бы вы объяснить, почему этот код выходит из строя? Я ожидал бы выход "a", но я получаю ошибку сегментации.

#include <functional>
#include <iostream>
#include <vector>
#include <string>


using namespace std;

struct MyStruct {
  vector<string> a;
  vector<string> b;
};

void out_data(const MyStruct& my_struct, const std::function<const vector<string>&(const MyStruct&)> getter) {
  cout << getter(my_struct)[0] << endl;
}

int main(int argc, char** argv)
{
  MyStruct my_struct;
  my_struct.a.push_back("a");
  my_struct.b.push_back("b");
  out_data(my_struct, [](const MyStruct& in) {return in.a;});
  return 0;
}

Ответ 1

[](const MyStruct& in) {return in.a;}

lambda выражение эквивалентно

[](const MyStruct& in) -> auto {return in.a;}

который возвращает копию in.a. Ваша подпись std::function затем возвращает ссылку на локальный объект.


Измените лямбда-выражение на

[](const MyStruct& in) -> const auto& {return in.a;}

чтобы вернуть const& вместо этого, установив segfault.


Кроме того, не используйте std::function для пропуска lambdas, если у вас нет веских оснований для этого. Я предлагаю прочитать статью на эту тему: передача функций в функции.

Ответ 2

Я обвиняю std::function (и вас). Я обвиняю вас, конечно, в том, что вы просите std::function вернуть оборванную ссылку, как объяснил Витторио Ромео. Но я также обвиняю шаблон конструктора std::function для того, чтобы не проверять этот случай, который должен быть в большинстве случаев или во всех случаях обнаруживаться во время компиляции и, следовательно, генерировать диагностику. (Я использую слово "винить", чтобы указать на возможные области улучшения. В этом смысле я также виню себя за то, что не подумал добавить эту точную проверку в реализацию моего собственного шаблона класса unique_function.)

Давайте более подробно рассмотрим подпись конструктора. Для этой цели я выбрал сайт определения.

template<typename R, typename... Args>
template<typename F>
std::function<R(Args...)>::function(F f);

Здесь должно быть возможно запретить ссылки. Отвязанная ссылка будет возвращена из operator() тогда и только тогда, когда R является ссылкой на временный объект, возвращаемый F. Пусть определите (в области тела конструктора):

using RF = decltype(f(std::forward<Args>()...));

Теперь мы можем быть почти уверены, что обратная ссылка будет возвращена из function operator(), если:

std::is_reference<R>::value && ! std::is_reference<RF>::value

Существует уловка, однако, что RF может быть типом класса с пользовательским оператором преобразования до R. Хотя это преобразование все еще может быть небезопасным, на данный момент мы не располагаем достаточной информацией для принятия решения и должны ошибаться в стороне общности. Очевидно, мы могли бы определить, является ли целевой объект R общедоступным базовым классом RF (это предполагает, что указанное условие истинно):

std::is_convertible<RF *, typename std::remove_reference<R>::type *>::value

Я разрешаю только наследование наследования, потому что std::function может обращаться только к открытым недвусмысленным базовым классам. Если кто-то не сделал std::function a friend из RF по какой-то странной причине. (Поскольку преобразование может быть выполнено внутри объекта завернутой функции, вероятно, нет необходимости делать это.)

Объединяя все это и инвертируя логику, мы можем префикс тела конструктора function:

using RF = decltype(f(std::forward<Args>()...));
static_assert( ! std::is_reference<R>::value  ||
                 std::is_reference<RF>::value ||
               ! std::is_convertible<RF *, typename std::remove_reference<R>::type *>::value,
               "Using this function object would result in a dangling reference in the function call" );