1

I was trying to understand how std::function works and created what started being a simple example:

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

using namespace std::placeholders;  // _1, _2, _3...

class holder
{
public:
    using fn_decoder = std::function< int ( uint8_t input, int event ) >;
    using raw_fn_decoder = int ( holder::* )( uint8_t input, int event );

    fn_decoder  _fn_decoder;

    holder( )
    :   holder( std::bind( &holder::decode, this, _1, _2 ) )
    {}
    holder( raw_fn_decoder dec )
    :   _fn_decoder( std::bind( dec, this, _1, _2 ) )
    {}
    holder( fn_decoder dec )
    :   _fn_decoder( dec )
    {}

    virtual int decode( uint8_t input, int event )
    {
        std::cout << "[" << __PRETTY_FUNCTION__ << "] '" << input << "', " << ( unsigned )event << std::endl;
        return event;
    }

    int operator( )( uint8_t input, int event )
    {   return _fn_decoder( input, event ); }
};

int decode( uint8_t input, int event )
{
    std::cout << "[" << __PRETTY_FUNCTION__ << "] '" << input << "', " << ( unsigned )event << std::endl;
    return event;
}

int main( )
{
    holder      h0;
    h0( 'A', 1 );
    h0._fn_decoder = std::bind( &decode, _1, _2 );
    h0( 'C', 3 );
}

And it outputs, as expected:

[virtual int holder::decode(uint8_t, int)] 65, 1
[int decode(uint8_t, int)] 67, 3

My next step was, then, add a derived class:

class new_holder
:   public holder
{
public:
    new_holder( )
    :   holder( static_cast< raw_fn_decoder >( &new_holder::decode ) )
    {}

    int decode( uint8_t input, int event )
    {
        std::cout << "[" << __PRETTY_FUNCTION__ << "] '" << input << "', " << ( unsigned )event << std::endl;
        return event;
    }
};

Then I added this lines to main():

new_holder  h1;

h1( 'E', 5 );
h1._fn_decoder = std::bind( &holder::decode, &h1, _1, _2 );
h1( 'F', 6 );
h1._fn_decoder = std::bind( &decode, _1, _2 );
h1( 'G', 7 );

h0._fn_decoder = std::bind( &new_holder::decode, &h1, _1, _2 );
h0( 'B', 2 );

And the output:

[virtual int holder::decode(uint8_t, int)] 'A', 1        // As before.
[int decode(uint8_t, int)] 'C', 3                        // As before.
[virtual int new_holder::decode(uint8_t, int)] 'E', 5    // As expected.
[virtual int new_holder::decode(uint8_t, int)] 'F', 6    // Shouldn't be holder::decode?
[int decode(uint8_t, int)] 'G', 7                        // As expected.
[virtual int new_holder::decode(uint8_t, int)] 'B', 2    // Well, it is weird, but I asked for.

I cannot understand the 'F' line. I thought I should be able to build a std::function out of holder::decode since it is done in h0 construction.

Here is a live running example, for anyone willing to try out:


Things get even worse if I try to subclass new_holder or derive another_holder from holder but I think it will get too verbose if I include it all here.


Thanks in advance

Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181
j4x
  • 3,595
  • 3
  • 33
  • 64
  • All the conditional compilation in your online compiler links makes them really hard to follow. If you minimize it further, it will be easier to understand – Justin Feb 15 '18 at 19:09
  • 3
    Use _lambda expressions_ instead of `std::bind`, that's so oldschool. –  Feb 15 '18 at 19:09
  • 2
    If I understand that correctly, you have something roughly equivalent to `mem_fn_ptr = &Base::virtual_method;`, you call `(derived.*mem_fn_ptr)(...)`, and your question is, "Why is the derived class virtual method called?" It's because the function is virtual... – Justin Feb 15 '18 at 19:11
  • Thanks for your answer @Justin and sorry for the `ifdefs`. It was the easiest way I could think to make everything fit into a runnable example. You are telling me that despite the fact I am explicitly taking the base class member function address to build a `std::function` object, my derived object will someway resolve it to its overriden? I will have to think a lot to understand it... – j4x Feb 15 '18 at 19:16
  • @TheDude, sorry but I am not smart enough to find out how to declare my `_fn_decoder` inside my class for use with lambdas. Could you please shed me some light? – j4x Feb 15 '18 at 19:22
  • @j4x I have first to look at that `std::placeholder` stuff (probably reference captures of some kind), though it's easy to capture `this` in a lambda expression and refer to the exact `decode()` function you want to have registered, without resolving it via the _`virtual` polymorphism_ introduced. –  Feb 15 '18 at 19:28
  • 2
    You don't need to use `std::bind`; you can convert `h0._fn_decoder = std::bind( &decode, _1, _2 );` into `h0._fn_decoder = [](uint8_t input, int event) { return decode(input, event); };`, or possibly even `h0._fn_decoder = [](auto input, auto event) { return decode(input, event); };`. The lambda is preferred nowadays. One reason is then people don't have to learn the `std::bind` syntax, and also because it's a lot easier for the compiler to optimize than `std::bind`. – Justin Feb 15 '18 at 19:37
  • @Justin And especially for the OP's use case, you can specify that you mean `holder::decode()` within the lambda expression, and circumvent the `virtual`. function call. –  Feb 15 '18 at 19:44
  • 2
    Very related - Possible duplicate: [Calling base class definition of virtual member function with function pointer](https://stackoverflow.com/q/1207106/1896169) – Justin Feb 15 '18 at 19:46
  • 1
    Interesting to note that the taking the address of a virtual member function for a BASE class, as you do here, doesn’t actually point to the meat of the function itself, but instead to a stub that first looks at the `this` pointer to pull up the proper (i.e, virtual) address to call. Because you pass a pointer to a derived class object, even though you’re binding to the base class virtual function, for this reason it still calls into the derived class implementation (as Justin more succinctly referenced). – Dan Nissenbaum Feb 15 '18 at 19:53
  • @TheDude if you have time can you mention how to reference `holder::decode` inside a lambda in a way that circumvents its virtual behavior? I couldn’t see it and wracked my brain a bit. – Dan Nissenbaum Feb 15 '18 at 20:02
  • @Dan I believe _@Yakk_ shows a way how. –  Feb 15 '18 at 20:14
  • Thank you very much for your answer @DanNissenbaum. I think this would give a correct answer as well as Yakk's. – j4x Feb 15 '18 at 23:16

1 Answers1

2

&new_holder::decode and &holder::decode are virtual member function pointers.

They are "pointers" into the "virtual function table", not pointers at the actual functions.

When you invoke using them, you do virtual dispatch.

Everything else you did is noise on top of that.

There is no direct syntax to get a pointer to a specific implementation of a virtual function. You can get close:

[](auto* self, auto&&...args)->decltype(auto) {
  return self->FIXED_TYPE::SomeMethod( decltype(args)(args)... );
}

if you want ->* syntax you have to overload the operator and the like, but that could be passed to a std::function and it would "do the right thing" and invoke a specific overload instead of doing overload resolution.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks for your answer @Yakk. I never meant to call any specific version of a virtual function but just understand the behaviour of my test program. – j4x Feb 15 '18 at 23:17