Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I populate variables through a std::map before main()?

I stumbled across some peculiar behavior in an old program and am figure out why G++ and CLang++ allow it to happen. I have some global variables declared and initialized before main(). The odd part is that they are initialized through a static std::map which is being populated at the same time using the subscripting operator. Everything seems to be in the correct place as soon as main() runs, with the size of the map showing the correct number of populated items as well as the variables containing the values shown before main().

#include <map>
#include <iostream>

static std::map<int, const char*> staticMap;

const char* const a = staticMap[0] = []()->const char* {return "a";}();
const char* const b = staticMap[1] = []()->const char* {return "b";}();
const char* const c = staticMap[2] = []()->const char* {return "c";}();
const char* const d = staticMap[3] = []()->const char* {return "d";}();
const char* const e = staticMap[4] = []()->const char* {return "e";}();

int main() {
    std::cout << "# Items: " << staticMap.size() << '\n' << std::endl;

    std::cout << "Values:\n";
    std::cout << "\"a\" = " << a << '\n';
    std::cout << "\"b\" = " << b << '\n';
    std::cout << "\"c\" = " << c << '\n';
    std::cout << "\"d\" = " << d << '\n';
    std::cout << "\"e\" = " << e << '\n';

    std::cout << std::endl;

    std::cout << "Map Contents:" << std::endl;;
    for (unsigned i = 0; i < 5; ++i) {
      std::cout << "\t" << staticMap[i] << std::endl;
    }

    return 0;
}

Here is the result after trying both G++ and CLang (I used the flags -std=c++11 -Wall -Werror -Wextra -pedantic-errors):

# Items: 5
Values:
"a" = a
"b" = b
"c" = c
"d" = d
"e" = e
Map Contents:
    a
    b
    c
    d
    e

Is this something inherently allowed in C++? I even went so far as to create my own map type and got the same results but am still not sure if it's behavior I can rely on.

like image 204
icdae Avatar asked Dec 11 '22 02:12

icdae


2 Answers

That is perfectly correct code, albeit a bit unusual. The standard guarantees that globals in the same TU are initialized in declaration order (so staticMap will be constructed before the other initializations happen), and including a call to an overloaded operator in an initialization expression is nothing strange.

Actually, all that lambdas stuff is overkill, you could simply do:

const char* a = staticMap[0] = "a";
const char* b = staticMap[1] = "b";
const char* c = staticMap[2] = "c";
const char* d = staticMap[3] = "d";
const char* e = staticMap[4] = "e";

Or, even simpler:

const char *dummy = (
    staticMap[0]="a",
    staticMap[1]="b",
    staticMap[2]="c",
    staticMap[3]="d",
    staticMap[4]="e");

In general, you can execute all the code you want before main if you define a class for that:

class MyCode
{
    MyCode()
    {
        // here be your code
    }
};
static MyCode m;

But be careful, debugging code running before main starts is often a hairy mess that is best avoided. In all my recent projects, almost no code is run in constructors of global variables, more often I just create the relevant "globals" as locals to main and store a pointer to them in global variables.

like image 168
Matteo Italia Avatar answered Dec 31 '22 01:12

Matteo Italia


This is a legal, common technique to execute some code before entering main. In C++ only declarations are allowed at file/namespace scope, but a declaration may contain function calls (the call to std::map::operator[] is one)

Your example is effectively identical to :

int foo()
{
   std::cout << "foo";  // You could fill your map here 
   return 42;
}

int bar = foo(); // allowed

int main() {

    return 0;
}

But a more idiomatic C++ way is to use constructors to achieve the same effect :

struct foo
{
   foo()
   {
       std::cout << "foo"; // You could fill your map here  
   }
};

foo f;

int main() {

    return 0;
}

And you can use foo() to do your map insertions.

Note :

In your example all those lambdas are useless, it could be simplified to :

const char* const a = staticMap[0] = "a";
const char* const b = staticMap[1] = "b";
const char* const c = staticMap[2] = "c";
const char* const d = staticMap[3] = "d";
const char* const e = staticMap[4] = "e";
like image 28
quantdev Avatar answered Dec 31 '22 00:12

quantdev