14

I have a std::function pointing to a function. Inside this function I change the pointer to another function.

std::function<void()> fun;

void foo() {
    std::cout << "foo\n";
}

void bar() {
    std::cout << "bar\n";
    fun = foo;
}

int main() {
    fun = bar;
    fun();
    fun();
}

I can't see any problem and it works just fine (see here), however I'm not sure if this is legal to do so. Is there anything I am missing? Maybe in the c++ standard draft (I checked quickly but didn't see anything so far).

user1810087
  • 5,146
  • 1
  • 41
  • 76
  • 7
    There's nothing wrong with this. That's it. – Sam Varshavchik Mar 07 '17 at 14:40
  • 2
    Legal but bad style, due to the hard to control state of the global variable (which you should try to avoid anyhow). – Aziuth Mar 07 '17 at 14:50
  • 1
    I have a mini game project where I use `std::function` as main loop callback (called 60 times/sec) and have it assign to different methods to change game states (menu selecting, playing, pausing, etc.). It runs fine. – tnt Mar 07 '17 at 15:03
  • @Aziuth thanks, but in the real code `fun`, `foo` and `bar` are members of a class. The code i posted should just represent the functionality. – user1810087 Mar 07 '17 at 15:23

2 Answers2

12

This is legal with function pointers.

When you assign or construct a std::function with a target, it creates a copy of the target. In the case of assigning a function to the std::function, this in effect stores a function pointer as the target object.

When you invoke operator(), it is required to return what would happen if you invoked that the target with the arguments.

Within that "body" of the copy of the function object stored as a copy in the std::function, if you reassign to the std::function, this destroys the old target function object.

Destroying a function pointer has no impact on the validity of code executed within the function pointed to.

However, if you had stored function objects (lambdas, manual ones, other std::functions, std::bind, etc), at the point of assignment you'd run into the usual rules of running a method in a class when this is destroyed. In short, you would no longer be able to do anything that relied on "local state" of your instance.

std::function<void()> fun;
struct bob {
  std::string name;
  bob* next = 0;
  void operator()() const {
    std::cout << name << "\n";
    if (next) fun = *next;
    // undefined behavior:
    // std::cout << name << "\n";
  }
};
bob foo = {"foo"};
bob bar = {"bar", &foo};

int main() {
  fun = bar;
  fun();
  fun();
}

live example.

So as you can see, this can be fragile.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
-1

It may come back to bite you if you do it without due consideration and in code documentation, but there is no logical reason why it won't work.

In c++, the address of a function is not needed, either within the function in the return coding.

If it didn't work in some language the complier probably wouldn't accept it - if it's a half decent compiler.

Quandon
  • 27
  • 4
  • 4
    Wouldn't agree with the last sentence in general. In another case, it might be undefined behaviour that the compiler is able to handle like the programmer wanted but is still undefined. – Aziuth Mar 07 '17 at 14:51
  • A half decent compiler won't accept something where the behaviour may be undefined. In years gone by c compilers would accept the most horrendous code. Fortunately, most c++ compilers are a very great deal stricter. – Quandon Mar 07 '17 at 15:01
  • 1
    `int main(){return *(const int*)(nullptr);}`, compile with `g++ -std=c++14 -O2 -Wall -Werror`, [no warning, no error, SIGSEGV](http://coliru.stacked-crooked.com/a/c15b3725d0b78f59). – YSC Mar 07 '17 at 15:05
  • So you're saying there are no half decent compilers. Because there are no compilers which will catch all forms of undefined behavior. – Benjamin Lindley Mar 07 '17 at 15:05
  • `int* foo(){ int a = 1; int* p_a = &a; return p_a;} int main(){ cout << *foo() << endl;}` <<< so, your compiler rejects this? Unlike the direct null pointer dereference, this is something what a beginner might easily do, not realizing that `a` dies (although even the null pointer dereference could be done, if something is not initialized properly for example). Here is some more fun: http://stackoverflow.com/questions/367633/what-are-all-the-common-undefined-behaviours-that-a-c-programmer-should-know-a << tell me how many of those your compiler does reject. – Aziuth Mar 07 '17 at 15:08
  • You are misinterpreting what I said. My intent was to say that if there was something about the language and its runtime system that meant that this could never work, there is a good chance it would be picked up. I was in no way intending to imply that compilers are idiot proof. – Quandon Mar 07 '17 at 15:15
  • So your former statement was clear and clearly wrong. Your new statement is vague, but still probably wrong. – Benjamin Lindley Mar 07 '17 at 15:20
  • The last sentence is plain wrong. If there would be a problem, it would be a lifetime problem (i.e. at runtime) which compilers generally cannot detect. In particular, compilers aren't expected to know the states of complex objects like `std::function`. Since this is a global, that state could be literally anything. – MSalters Mar 07 '17 at 17:12