0

(>= c++14)

I'm currently using a namespace World as (kind of) a static class. Here's a little (and dumb) example of what's wrong :

world.hpp

#pragma once

namespace World {
    namespace {
        template<typename T>
        std::vector<T> components ;
    }

    template<typename T>
    T get(int i) {
        return components<T>[i] ;
    }

    template<typename T>
    void add (T elm) {
        components<T>.push_back(elm) ;
    }
}

layer.hpp

#pragma once

class Layer {
    public:
        float get(int i) ;
        void add(float elm) ;
} ;

layer.cpp

#include "layer.hpp"
#include "world.hpp"

float Layer::get(int i) { return World::get<float>(i) ; }
void Layer::add(float elm) { World::add<float>(elm) ; }

main.cpp

#include "world.hpp"
#include "layer.hpp"
#include <iostream>

int main() {

    Layer layer ;

    for (int i=0 ; i<5 ; ++i) {
        World::add<int>(i) ;
        layer.add(i/5.f) ;
    }

    for (int i=0 ; i<5 ; ++i) {
        std::cout << "get<int>:" << World::get<int>(i) << std::endl ;
        std::cout << "Layer::get:" << layer.get(i) << std::endl ;
        std::cout << "get<float>:" << World::get<float>(i) << std::endl ; // <-- ! SEGFAULT
    }

    return 0 ;
}

I understand that each time my world.hpp file get included in a .cpp, it will create a new variable components so when I call layer.get and World::get it will not access the same vector (although this is what I want). I guess it has something to do with static or external linkage (or am I wrong ?) but I have absolutely no idea how to deal with it. What am I missing ?

[Edit] What I want is that the vector components is unique, so every file that include "world.hpp" refer to the exact same vector.

  • 2
    Each time `world.hpp` gets included it create a __new__ unnamed namespace nested inside `namespace World` this is probably not what you want. – Richard Critten Nov 10 '19 at 13:35
  • Can you explain what you meant "how to deal with it". If you don't want each translation unit to be using an anonymous namespace here, then don't use anonymous namespaces. – Sam Varshavchik Nov 10 '19 at 13:40
  • @RichardCritten indeed but since I have templated functions I can't move the unnamed namespace to a .cpp file. SamVarshavchik I tried to make my goal a bit clearer. – Timothée Chabat Nov 10 '19 at 13:48
  • Why is there an anonymous namespace at all? – Richard Critten Nov 10 '19 at 13:50
  • @RichardCritten to hide the variables from the outside but since it's causing a lot of problems i guess I'll remove it if there's no other solution – Timothée Chabat Nov 10 '19 at 14:08

1 Answers1

1

your problem has not much to do with file inclusion or linkage. components is a variable template, not an actual variable. For each type you instantiate it (in your case float and int), explicitly or implicitly, the compiler will create a variable for you.

So what you basically have:

  • with World.add<int>(...) in main.cpp you create the variable World::components<int> and fill it with values.
  • with layer.add(...) in main.cpp you call World::add<float> in layer.cpp which will create the variable World::components<float>

Both are distinct variables, this is how templates work. The same is happening for your function templates in World. For each type the templates are instantiated, the compiler will create a new function for you.

Edit after comments:

Maybe I still got something wrong, but

  • layer.get(i) and
  • world::get<float>(i)

give me the same value, the one added with layer.add(i/5.f) so it works like it should, doesn't it?

Both give the values in World::components<float> whereas World::get<int>(i) gives me the values in World::components<int>

Output for the program (after fixing some small errors):

get<int>:0
Layer::get:0
get<float>:0
get<int>:1
Layer::get:0.2
get<float>:0.2
get<int>:2
Layer::get:0.4
get<float>:0.4
get<int>:3
Layer::get:0.6
get<float>:0.6
get<int>:4
Layer::get:0.8
get<float>:0.8

Second Edit (digging):

So as something should or should not work (and not only sometimes) I started digging. Indeed the compiler creates two instances of World::components<float>. After

g++ -o prog layer.cpp main.cpp

and

nm prog | grep components

I get

0000000000407210 b _ZN5World12_GLOBAL__N_110componentsIfEE
0000000000407230 b _ZN5World12_GLOBAL__N_110componentsIfEE
00000000004071f0 b _ZN5World12_GLOBAL__N_110componentsIiEE

which tells me two components<float> and one component<int> reside in the BSS section of the object file.

Depending of the order of compilation, the program works either as expected or produces a segmentation fault. The order state above works,

g++ -o prog main.cpp layer.cpp

results in the segmentation fault reported by the op. Without checking the access to all three components in the compiled file, I assume the compilation of main.cpp creates the float and int version of the vector, the compilation of layer.cpp creates the second float version of the vector. The first two are initialized in main.cpp, while the third remains empty - leading to undefined behavior when it is accessed with the [] operator later. It seems if compiled the other way around, the linker resolves every access to components<float> to the same instance in the BSS section, so the program is actually running fine, but will probably produces unexpected/undefined behavior under different circumstances (e.g., if the second instance of components<float> is accessed somewhere else somehow.

So, a solution is needed. I've change world.hpp to

#pragma once

#include <vector>

namespace World {
    template<typename T>
    struct Comp {
        static std::vector<T> components ;
    };  

    template<typename T>
    T get(int i) {
        return Comp<T>::components[i] ;
    }   

    template<typename T>
    void add (T elm) {
        Comp<T>::components.push_back(elm) ;
    }   
}

template<typename T>
std::vector<T> World::Comp<T>::components;

which works, regardless of the order of compilation/linkage.

Third edit (remarks): Btw. I read in your comments below your question that you want to hide components from the outside or restrict access to it. If this is your goal you could also do the following:

  • Change World from namespace to class
  • Declare components as private and static member
  • Declare your two function as public and static

I that case you also have to define your private static member the way I did in the example above (last two lines).

Hans Hohenfeld
  • 1,729
  • 11
  • 14
  • Yes I get this part. But the World::::compenents created by World::add is not the same that the one created by Layer::add(float), which is not the behavior I wanted. – Timothée Chabat Nov 10 '19 at 14:27
  • Ah, ok, sorry, I should've compiled your code and tested it before stating the obvious. I'll check. – Hans Hohenfeld Nov 10 '19 at 14:33
  • Well yeah this looks right ! But it still doesn't work on my computer unless I remove the unnamed namespace .. can you tell me what changes you made and the compiler you're using ? – Timothée Chabat Nov 10 '19 at 15:03
  • I use gcc-Version 9.2.1 20190827 (Red Hat 9.2.1-1) (GCC), no additional flags provided. I changed world to World in main.cpp (typo) and added the missing semicolon after the class definition in layer.hpp. I'm currently checking the compiled object files to get to the bottom of this. – Hans Hohenfeld Nov 10 '19 at 15:12
  • I tried again and now it's working, although I don't know what changed ... I'm very very sorry for losing your time. If I find out what changed between before and now, I'll let you know about it. Thanks for the time you spent. – Timothée Chabat Nov 10 '19 at 15:24
  • There actually was an error, I extended my answer after some further digging – Hans Hohenfeld Nov 10 '19 at 15:35
  • I used a namespace over a "static class" for the reason exposed [here](https://stackoverflow.com/questions/5793334/c-static-vs-namespace-vs-singleton) even tho I couldn't apply the exact solution because of my templated functions. Anyway, this works great now. – Timothée Chabat Nov 10 '19 at 16:06