Search code examples
c++boostboost-spiritboost-spirit-x3

How to deal with Boost Spirit X3 causing a "static initialization order fiasco" in Visual Studio 2019?


I'm working on a nontrivial parser in C++ on top of boost::spirit::x3. I am splitting up my parsing code into logical units some of which are dependencies of each other. For example, one unit is an expression parser that also exposes an indentifier parser. Many higher level syntactic constructs of the target language include expressions and identifiers so this unit is frequently a dependency. I have split the code into triples of files as the documentation recommends. If there are units foo, bar, and quux, I have files like:

parser
    foo.h
    foo_def.h
    foo.cpp
    bar.h
    bar_def.h
    bar.cpp
    quux.h
    quux_def.h
    quux.cpp

where the .h expands BOOST_SPIRIT_DECLARE; the _def.h expands BOOST_SPIRIT_DEFINE, and the .cpp expands BOOST_SPIRIT_INSTANTIATE. I have run into a persistent problem in which my code errors out at startup due to static initialization fiasco in Spirit code (line 160 of .../x3/nonterminal/rule.hpp) but only when running a debug build.

Based on stackoverflow questions and answers like this one and this one plus the fact that the code executes without error when built in the release configuration, I believe there is nothing wrong with my code. I can see two workarounds if there is no way to fix my code in debug:

  1. Only in debug builds use a pragma declaration to change the order of static initialization. (Changing the order of the items in the .vcxproj file did not affect the initialization order) In Visual Studio if I put a #pragma init_seg(lib) in the .cpp file of my expression parser the problem goes away.

  2. Only in debug builds include superfluous _def.h files where necessary e.g. if quux depends on bar above then including bar_def.h in quux.cpp will fix the problem, but defeats the purpose of splitting the parser into multiple files.

I am wondering generally what the state of this problem is? It seems to be a known issue but has been around a long time and so I should not expect it to be fixed at the Spirit level? Is there some better way to structure my code such that this problem does not occur? I was wondering if it would be possible to, for example, create all parsers and their child parsers as static variables in function scope created on first use but there is no examples like this in the literature so I am unsure if it is possible?


Solution

  • The usual way is to have functions returning local statics by reference.

    Function-local statics

    • have static "lifetime" (storage duration) but will only be initialized on first use (dodging the fiasco)
    • since C++11 you can even rely on initialization to be thread-safe

    So, you might have in the header:

    my_rule_type const& my_rule();
    

    And in the cpp that defines the rule:

    my_rule_type const& my_rule() {
         static const my_rule_type s_my_rule = ns::my_rule;
         return s_my_rule;
    }
    

    UPDATE

    Actually the documentation example doesn't bother with returning references, instead returning a copy each time:

    parser::employee_type employee()
    {
        return parser::employee;
    }
    

    This suggests that there won't be significant overhead in doing so, and I recommend you avoid the complication of reference-semantics there.