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?
CodePudding user response:
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::function
s are objects, not functions.