qft
qft

Reputation: 715

Why passing an object derived from std::function by reference crashes the program?

If I pass an object that's derived from std::function to another function by reference, the program always crashes during runtime with a bad_function_call error. As the following code shows:

#include<functional>
#include<iostream>

class foo :public std::function<int(void)> {
    public:
        int operator()(){return 1;}
};

void bar(const std::function<int(void)>& f) {std::cout << f() << std::endl;}

int main(){
    foo f;
    bar(f);
}

However if the functional object is passed by value like this:

void bar(std::function<int(void)> f)

the program runs ok. I've tested the program on gcc, clang and visual studio and the results are the same. What is causing this bad_function_call?

Upvotes: 3

Views: 887

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275976

std::function::operator() is not virtual.

class foo :public std::function<int(void)> {
  public:
    int operator()(){return 1;}
};

when viewing a foo as a std::function, the operator() you wrote does nothing. You have an empty std::function<int()>, not one that returns 1.

std::function does type-erasure based polymorphism, not inheritance based polymorphism. It can store anything it can invoke, copy and destroy. You can pass it around by value, and the stored callable will be brought around with it.

Inheriting from it is usually not what you want to do.

class foo {
  public:
    int operator()(){return 1;}
};

This can be converted into a std::function. In fact, with this change your code compiles and works.

Without this change, it prefers to cast-to-base and pass a reference to the (empty) base std::function to the argument. Without inheritance, it instead attempts to convert the foo to a std::function and succeeds.

Of course, this foo is pretty silly.

Instead of this:

int main(){
  foo f;
  bar(f);
}

we can do this:

int main(){
  auto f = []{ return 1; };
  bar(f);
}

and it works just as well. (The lambda above auto-generates a class that is almost identical to the foo type above in the important ways. It doesn't inherit from std::function either.)

C++ supports more than one kind of polymorphism. Inheritance based polymorphism doesn't (easily) permit value-types, and C++ thrives on value-types, so std::function was written to work with value-types.


As @T.C. noted below,

void bar(std::function<int(void)> f)

this works because the compiler is choosing between "slicing" to the base class and using a converting constructor, and the converting constructor is preferred by the C++ standard.

void bar(std::function<int(void)> const& f)

here it isn't preferred, because no conversion need be done, just "treat as base", and that is higher priority than constructing a new object in the rules.

In the case where we pass a lambda or an "unparented" foo, the "take a reference to parent" case isn't available, so a temporary std::function is created from our foo (or lambda) and f binds to it.

Upvotes: 8

Jarod42
Jarod42

Reputation: 218343

Your overload is not used, the const one (from std::function) is used with const std::function<int(void)>& f. and as you don't initialize std::function, it is empty and then throws when operator () is called.

When you pass by value, a std::function is created from your functor (as any regular functor), and then std::function invoke your stored functor.

But don't derive from std::function, just use

auto f = [](){ return 1; };
bar(f);

Upvotes: 4

VINOTH ENERGETIC
VINOTH ENERGETIC

Reputation: 1853

bar(f);

You are passing f of type foo to bar, but parameter of bar is const std::function<int(void)>

Here foo is upcasted to const std::function<int(void)>.

You haven't overloaded operator() in const std::function<int(void)>.

So it is throwing runtime error.

Upvotes: 1

Related Questions