Неожиданное поведение после назначения объекта функции оболочке функции

Я искал ошибку в приложении, которую я наконец исправил, но не совсем понял. Поведение может быть воспроизведено с помощью следующей простой программы:

#include <iostream>
#include <memory>
#include <functional>

struct Foo
{
  virtual int operator()(void) { return 1; } 
};

struct Bar : public Foo
{
  virtual int operator()(void) override { return 2; }
};

int main()
{
    std::shared_ptr<Foo> p = std::make_shared<Bar>();
    std::cout << (*p)() << std::endl;

    std::function<int(void)> f;
    f = *p;
    std::cout << f() << std::endl;

    return 0;
}

Вывод строки

std::cout << (*p)() << std::endl;

это 2, что, как я и ожидал, конечно.

Но вывод строки

std::cout << f() << std::endl;

это 1. Это меня удивило. Я даже был удивлен, что назначение f = *p разрешено и не вызывает ошибку.

Я не прошу обходной путь, потому что я установил его с помощью лямбды.
У меня вопрос: что происходит, когда я делаю f = *p и почему вывод 1, а не 2?

Я воспроизвел проблему с помощью gcc (MinGW) и Visual Studio 2019.
Далее я хочу упомянуть, что вывод

Bar b;
std::function<int(void)> f1 = b;
std::cout << f1() << std::endl;

снова 2.

Ответы

Ответ 1

Нарезка объектов происходит здесь.

Дана точка f = *p;, p имеет тип std::shared_ptr<Foo>, затем тип *p - Foo& (вместо Bar&). Даже оператор присваивания std::function принимает аргумент по ссылке, но

4) Устанавливает цель *this для вызываемой f, как если бы она выполнялась function(std::forward<F>(f)).swap(*this);.

Обратите внимание, что F выше также выводится как Foo&. И конструктор из std::function принимает аргумент по значению, происходит разрезание объекта, возникает эффект, что f назначается из объекта типа Foo, который копируется фрагментом из *p.

template< class F > 
function( F f );

Ответ 2

Это обычная нарезка, скрытая под слоем std::function и std::shared_ptr.

f = *p;

допустимо, потому что *p является вызываемым объектом с соответствующим operator(), и это одна из вещей, которую вы можете заключить в std::function.

Причина, по которой он не работает, заключается в том, что он копирует *p - и это Foo&, а не Bar&.

Эта адаптация вашего последнего примера будет вести себя так же:

Bar b;
Foo& c = b;
std::function<int(void)> f1 = c;
std::cout << f1() << std::endl;

Ответ 3

нарезка

Это случай нарезки. Причина - оператор присваивания std::function (как показано в другом ответе), который гласит:

Устанавливает цель * this для вызываемого f, как будто путем выполнения Функция (станд :: вперед (е)). подкачки (* это) ;. Этот оператор не участвовать в разрешении перегрузки, если f не вызывается для аргумента типы Args... и возвращаемый тип R. (начиная с С++ 14)

https://en.cppreference.com/w/cpp/utility/functional/function/operator%3D

Если вы упростите и урезаете пример - вы легко увидите, что происходит:

Foo* p =  new Bar;

Foo f;
f = *p;//<-- slicing here since you deref and then copy the object

Похоже, вы стремились получить указатель на переопределенную виртуальную функцию - к сожалению, нет простого способа развернуть поиск виртуальной функции, как это реализовано с помощью таблицы поиска во время выполнения. Тем не менее, простой обходной путь может заключаться в использовании лямбды для переноса (как упоминается в ОП):

f = [p]{return (*p)();};

Более подходящим решением может быть только использование reference_wrapper:

f = std::ref(p);

Ответ 4

Статический тип указателя p - Foo.

Так что в этом заявлении

f = *p;

там левый операнд *p имеет тип Foo, то есть есть разрезание.