Не могли бы вы объяснить, почему этот код выходит из строя? Я ожидал бы выход "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" );