c++std-functionlinkage

Constructing std::function from extern functions gives std::bad_function_call


I am experimenting with making pure Haskell-style I/O in C++. It's working correctly, but when I reorganize some definitions, I run into a std::bad_function_call.

This is about as much as it takes to trigger the problem:

//common.h

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

class Empty {};
class State {};

template <class A>
class IOMonad {
public:
  typedef std::function<std::pair<A, State> (State)> T;
};

template <class A, class B>
const auto bind(typename IOMonad<A>::T ma, std::function<typename IOMonad<B>::T (A)> f) {
  return [ma, f] (State state) {
    const auto x = ma(state);
    return f(x.first)(x.second);
  };
}

extern const IOMonad<std::string>::T getLine;
IOMonad<Empty>::T putLine(std::string str);
//externs.cpp

#include "common.h"

const IOMonad<std::string>::T getLine = [](State s) {
  (void)s;
  std::string str;
  std::cin >> str;
  return std::make_pair(str, State());
};

IOMonad<Empty>::T putLine(std::string str) {
  return [str] (State s) {
    (void)s;
    std::cout << str;
    return std::make_pair(Empty(), State());
  };
}
//main.cpp

#include "common.h"

const auto putGet = bind<std::string, Empty>(getLine, putLine);

int main() {
  (void)putGet(State());
  return 0;
}

With this setup, I get a std::bad_function_call when putGet is called. Previously, I had the contents of externs.cpp in main.cpp between including common.h and defining putGet, and everything worked fine. Something about having those functions in a different translation unit seems to be causing this problem. Also, if I keep the functions in externs.cpp, but I make putGet a local variable to main instead of a global variable, this does not happen. Another thing that makes the exception go away is folding the definition of bind into the definition of putGet, like so:

const auto putGet = [] (State state) {
  const auto x = getLine(state);
  return putLine(x.first)(x.second);
};

Why is this happening? Does std::function have some limitations I don't know about?


Solution

  • You've run afoul of the static initialization order fiasco. In your case, getLine is yet uninitialized when it's used to initialize putGet.

    The cardinal rule of C++ global variables is: Global variables must not depend on global variables in other compilation units for their initialization.

    While global variables in a single compilation unit are initialized in the order they're defined, the order in which global variables in different compilation units are initialized is unspecified. There is no guarantee that getLine will be initialized before putGet (and indeed, it seems it wasn't).

    To work around this, you need to either (two of which you've already found):

    A. Move the initialization of putGet into main so that getLine is guaranteed to be initialized before it's used

    or

    B. Don't use getLine directly in the initialization of putGet (i.e. wrap it in an extra layer of lambda).

    or

    C. Make getLine an actual function instead of a std::function holding a lambda. Objects and functions are fundamentally different in C++ and have different rules governing their lifetime. Despite their name, std::functions are objects, not functions.