4

Case where the problem occours

Please consider the following c++ code:

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

// Superclass
class A {
    public:
    virtual std::string get() const {
        return "A";
    }
};

// Subclass
class B : public A {
    public:
    virtual std::string get() const {
        return "B";
    }
};

// Simple function that prints the object type
void print(const A &instance) {
    std::cout << "It's " << instance.get() << std::endl;
}

// Class that holds a reference to an instance of A
class State {
    A &instance;
    public:
    State(A &instance) : instance(instance) { }
    void run() {

        // Invokes print on the instance directly
        print(instance);

        // Creates a new function by binding the instance
        // to the first parameter of the print function, 
        // then calls the function. 
        auto func = std::bind(&print, instance);    
        func();
    }    
};

int main() {
    B instance;
    State state(instance);

    state.run();
}

In this example, we have two classes A and B. B inherits from class A. Both classes implement a simple virtual method that returns the type name.

There is also a simple method, print, that accepts a reference to an instance of A and prints the type.

The class State holds a reference to an instance of A. The class also has a simple method that calls print by two different means.

Where it gets odd

The sole method in state first calls print directly. Since we supply an instance of B int the main method, the output is It's B, as expected.

For the second call, however, we bind the instance to the first parameter of print using std::bind. Then we call the resulting function without any arguments.

In this case, however, the output is It's A. I would have expected the output It's B, as before, since it is still the same instance.

If I declare the parameters as pointers instead of references, std::bind works as expected. I also placed some logging into the constructors of both classes to verify that no instances are created accidentally.

Why does this happen? Does std::bind discard some type information in this case? To my understanding, this must not happen since the method invocation should be managed by a vtable lookup via runtime.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Emiswelt
  • 3,909
  • 1
  • 38
  • 56

1 Answers1

6

This is just object slicing. Pass the instance by reference:

auto func = std::bind(&print, std::ref(instance));
//                            ^^^^^^^^

To explain this a bit more: Like most C++ standard library types, the result type of a bind expression owns all its bound state. This means you can take this value and pass it around freely and store it and come back to it later in a different context, and you can still call it with all its bound state ready for action.

Therefore, in your code, the bind object was constructed with a copy of instance. But since instance wasn't a complete object, you caused slicing to happen.

By contrast, my code copies a std::reference_wrapper<A> into the bind object, and that's essentially a pointer. It doesn't own the instance object, so I need to keep it alive as long as the bind object may get called, but it means that the bound call is dispatched polymorphically to the complete object.

Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Why is it not already passed as reference? The variable given to std::bind is a reference, the argument for print is a reference too. To additional/temporaty instances are constructed. – Emiswelt Dec 19 '15 at 01:08
  • 2
    @Emiswelt: The magic question is "what is *it*". *Something* is passed by reference, no doubt, but not what you think. In your case, the bind object itself holds a *sliced copy* of your instance (which it duly passes on by reference), whereas in my code it holds a reference wrapper. – Kerrek SB Dec 19 '15 at 01:10
  • 1
    @Emiswelt `std::bind` will pass its parameter either by copy or by move. Exception is `std::reference_wrapper`. – 101010 Dec 19 '15 at 01:10
  • Note that `bind` actually has special handling for `reference_wrapper`s - it always unwraps them. – T.C. Dec 19 '15 at 01:31
  • 1
    @T.C.: You mean via *INVOKE*? – Kerrek SB Dec 19 '15 at 01:40
  • @KerrekSB No, before it gets to INVOKE. See first bullet in [func.bind.bind]/10. – T.C. Dec 19 '15 at 01:42
  • @T.C.: Ah yes, the `get()` is built into `bind` -- you don't rely on the conversion function. – Kerrek SB Dec 19 '15 at 02:15
  • @T.C.: By the way, will you update the cppreference page on `std::invoke`? – Kerrek SB Dec 19 '15 at 02:15