2

Consider the following code in C++17:

#include <iostream>
#include <functional>

struct base
{
    base() {std::cout << "base::base" << std::endl;}
    virtual ~base() {std::cout << "base::~base" << std::endl;}
    virtual void operator()() {std::cout << "base::operator()" << std::endl;}
};

struct derived1: base
{
    derived1() {std::cout << "derived1::derived1" << std::endl;}
    virtual ~derived1() {std::cout << "derived1::~derived1" << std::endl;}
    virtual void operator()() {std::cout << "derived1::operator()" << std::endl;}
};

struct derived2: base
{
    derived2() {std::cout << "derived2::derived2" << std::endl;}
    virtual ~derived2() {std::cout << "derived2::~derived2" << std::endl;}
    virtual void operator()() {std::cout << "derived2::operator()" << std::endl;}
};

int main(int argc, char* argv[])
{
    base* ptr1 = new derived1();
    base* ptr2 = new derived2();
    std::function f1(*ptr1);
    std::function f2(*ptr2);
    std::invoke(*ptr1);     // calls derived1::operator()
    std::invoke(*ptr2);     // calls derived2::operator()
    std::invoke(f1);        // calls base::operator()
    std::invoke(f2);        // calls base::operator()
    delete ptr1;
    delete ptr2;
    return 0;
}

std::function does not seem to do the right thing with virtual functions. Would there be any way to make std::invoke(*ptrN) and std::invoke(fN) behave the same way? Or would there be any way to create a new function wrapper that would deal with virtual functions?

Vincent
  • 57,703
  • 61
  • 205
  • 388

4 Answers4

3

You can use a std::reference_wrapper, or the convenient std::ref. std::function will use SOO (small object optimization) in this case, so the object won't be copied/moved (avoiding the slicing problem). However, you won't get the deduction guide so you need to specify the template arguments.

std::function<void()> f1(std::ref(*ptr1));
std::function<void()> f2(std::ref(*ptr2));
user9143630
  • 163
  • 5
  • The avoidance of slicing is not by small object optimization, that does exactly what it claims to do: optimize. – Passer By Dec 27 '17 at 06:29
  • Why are deduction guides failing with std::reference_wrapper? – Vincent Dec 27 '17 at 06:39
  • @Vincent - Because the reference wrapper doesn't have `operator()` – StoryTeller - Unslander Monica Dec 27 '17 at 07:18
  • @StoryTeller It does: http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper/operator() – Vincent Dec 27 '17 at 07:32
  • @Vincent - A templated forwarding one. The deduction guide depends on the `operator()` being a member function, not a template of one. Roughly speaking. – StoryTeller - Unslander Monica Dec 27 '17 at 07:46
  • @Vincent http://en.cppreference.com/w/cpp/utility/functional/function/deduction_guides explains it well. `&F::operator()` needs to be well-formed in an unevaluated context. As `std::reference_wrapper::operator()` is a function template, you can't just take the address of it. It *could* still work if `std::reference_wrapper`'s definition was changed to include a non-template `operator()` in a similar way as that deduction guide: if and only if `&T::operator()` is well-formed in an unevaluated context. – oisyn Dec 27 '17 at 23:59
3

Would there be any way to make std::invoke(*ptrN) and std::invoke(fN) behave the same way? Or would there be any way to create a new function wrapper that would deal with virtual functions?

std::function copies its arguments to be able to run later, as already suggested in the comments to the question.
Therefore you can just avoid slicing objects and copy something else that is able to deal correctly with polymorphism.
As an example:

std::function f1([ptr1](){ (*ptr1)(); });
std::function f2([ptr2](){ (*ptr2)(); });
skypjack
  • 49,335
  • 19
  • 95
  • 187
1

your code doesn't pass instance of derived class to std::function, instead, it constructs new base object by copying instance of derived, which is passed to std::function.

Andrei R.
  • 2,374
  • 1
  • 13
  • 27
0

A simple way to avoid the slicing copy made by std::function is to bind the method operator() to the object it should be applied.

So you could write:

auto f1 = std::bind(&base::operator(), ptr1);
auto f2 = std::bind(&base::operator(), ptr2);
std::invoke(f1);                          // calls derived1::operator()
std::invoke(f2);                          // calls derived2::operator()
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Invoke can perfectly well "call the object pointed to by ptr1" (it will automatically invoke `operator()`, which was also shown in the example). The real problem is that `std::function` creates a slicing copy of that object - it stores a `base` because the passed in type was a `base`. You just need to avoid that copy. There are several ways to accomplish that: `std::bind()`, `std::ref()` and using lambda's are all acceptible answers. – oisyn Dec 27 '17 at 23:48
  • 1
    @oisyn: I'm too old for those semantics :-( I've edited my post... Thank you for the feedback. – Serge Ballesta Dec 28 '17 at 07:21