Pavel Kirienko
Pavel Kirienko

Reputation: 1292

Initialization of static members of class templates with side effects

My C++14 application needs to dynamically create and destroy objects of a certain type. Each of these objects is named, and the name is assigned when the object is constructed. Each name is hard-coded as a string literal. The following pseudocode demonstrates the idea:

int foo()
{
    NamedEntity entity1("Bar");
    NamedEntity entity2("Baz");

    // do some work

    return 42;
}

My objective is to create a constant list of all object names used in the application, and make it accessible for the application at run time.

The first naive solution that comes to mind is to grep the sources and autogenerate a header with a hard-coded list of names. Let's put this solution aside as a last resort option.

Can I create the list of object names at compile time? I am a big admirer of Mr. Alexanderscu's ways, so I thought, "Sure, why not, I'm going to craft a really clever user-defined string literal and call it a day".

The idea was as follows:

  1. Instantiate a new type for every object name using a user-defined string literal. We're going to call this type an object name type.
  2. Equip each of the instantiated object name types with a dummy static member. Initialize said static member with the return value of an arbitrary static function. We're going to call this function a registrator function.
  3. Define a singleton with a list of object names. Use the above mentioned registrator function to append names to the singleton's list.

The user-defined string literal was supposed to be applied roughly as follows:

int foo()
{
    NamedEntity entity1("Bar"_probe);
    NamedEntity entity2("Baz"_probe);

    // do some work

    return 42;
}

See _probe? This was supposed to be the solution.

Well, it didn't work. It turned out that the compiler defers initialization of the dummy static member up to the point where the user-defined string literal is actually invoked. This means that my list of names will remain incomplete until every single named entity was created at least once.

Why is it so? Clearly the registrator function has side effects (we need it only for the sake of its side effects), and therefore, according to cppreference, initialization of the dummy static member cannot be postponed:

Deferred dynamic initialization

It is implementation-defined whether dynamic initialization happens-before the first statement of the main function (for statics) or the initial function of the thread (for thread-locals), or deferred to happen after.

If the initialization of a non-inline variable is deferred to happen after the first statement of main/thread function, it happens before the first odr-use of any variable with static/thread storage duration defined in the same translation unit as the variable to be initialized. If no variable or function is odr-used from a given translation unit, the non-local variables defined in that translation unit may never be initialized (this models the behavior of an on-demand dynamic library). However, as long as anything from a TU is odr-used, all non-local variables whose initialization or destruction has side effects will be initialized even if they are not used in the program.

Below you will find a somewhat reduced MWE:

#include <iostream>

int g_foo = 0;

template <typename T, T... Chars>
class Registrator
{
    static int dummy_;

public:
    static int run()
    {
        g_foo++;
        static constexpr char str[sizeof...(Chars) + 1] = { Chars..., '\0' };
        std::cout << "Registering: " << &str[0] << std::endl;
        return g_foo;
    }
};

template <typename T, T... Chars>
int Registrator<T, Chars...>::dummy_ = Registrator<T, Chars...>::run();

template <typename T, T... Chars>
inline int operator""_probe()
{
    static constexpr char str[sizeof...(Chars) + 1] = { Chars..., '\0' };
    return Registrator<T, Chars...>::run();
}

int main(int argc, char**)
{
    std::cout << "g_foo=" << g_foo << std::endl;

    if (argc > 1)
    {
        std::cout << "Hello"_probe << std::endl;
        std::cout << "World"_probe << std::endl;
    }

    std::cout << "g_foo=" << g_foo << std::endl;

    return 0;
}

If it were to work correctly, you would have observed roughly the following output upon running it without arguments:

Registering: Hello
Registering: World
g_foo=2
1
2
g_foo=2

However, I am observing the following instead:

g_foo=0
g_foo=0

Which implies that the compiler did not initialize the dummy static members at all.

Running the program with at least one argument will force it to explicitly use the user-defined literal, and in this case, the dummy statics actually get initialized, but initialization is postponed until the point where the user-defined literal is used:

g_foo=0
Registering: Hello
1
Registering: World
2
g_foo=2

I'm using GCC 5.4.0 with -std=c++14. Why doesn't the compiler want to initialize my statics? Is this behavior correct? How do I work around that?

Upvotes: 0

Views: 208

Answers (1)

user7860670
user7860670

Reputation: 37582

You need to actually use dummy_ somehow, for example by taking its address:

static int run()
{
    &dummy_;

Run in online compiler

Also note that string literal operator templates are a GNU extension. And self-registration is probably not a good idea in general.

Upvotes: 2

Related Questions