Why should types be put in unnamed namespaces?

1.8k views Asked by At

I understand the use of unnamed namespaces to make functions and variables have internal linkage. Unnamed namespaces are not used in header files; only source files. Types declared in a source file cannot be used outside. So what's the use of putting types in unnamed namespaces?

See these links where it's mentioned that types can be put in unnamed namespaces:

3

There are 3 answers

5
Dietmar Kühl On BEST ANSWER

Where do you want to put local types other than the unnamed namespace? Types can't have a linkage specifier like static. If they are not publicly known, e.g., because they are declared in a header, there is a fair chance that names of local types conflict, e.g., when two translation units define types with the same name. In that case you'd end up with an ODR violation. Defining the types inside an unnamed namespace eliminates this possibility.

To be a bit more concrete. Consider you have

// file demo.h
int foo();
double bar();

// file foo.cpp
struct helper { int i; };
int foo() { helper h{}; return h.i; }

// file bar.cpp
struct helper { double d; }
double bar() { helper h{}; return h.d; }

// file main.cpp
#include "demo.h"
int main() {
     return foo() + bar();
}

If you link these three translation units, you have mismatching definitions of helper from foo.cpp and bar.cpp. The compiler/linker is not required to detect these but each type which is used in the program needs to have a consistent definition. Violating this constraints is known as violation of the "one definition rule" (ODR). Any violation of the ODR rule results in undefined behavior.

Given the comment it seems a bit more convincing is needed. The relevant section of the standard is 3.2 [basic.def.odr] paragraph 6:

There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then each definition of D shall consist of the same sequence of tokens; and [...]

There are plenty of further constraints but "shall consist of the same sequence of tokens" is clearly sufficient to rule out e.g. the definitions in the demo above from being legal.

9
R Sahu On

So what's the use of putting types in unnamed namespaces?

You can create short, meaningful classes with names that maybe used in more than one file without the problem of name conflicts.

For example, I use two classes often in unnamed namespaces - Initializer and Helper.

namespace
{
   struct Initializer
   {
      Initializer()
      {
         // Take care of things that need to be initialized at static
         // initialization time.
      }
   };

   struct Helper
   {
      // Provide functions that are useful for the implementation
      // but not exposed to the users of the main interface.
   };

   // Take care of things that need to be initialized at static
   // initialization time.
   Initializer initializer;
}

I can repeat this pattern of code in as many files as I want without the names Initializer and Helper getting in the way.

Update, in response to comment by OP

file-1.cpp:

struct Initializer
{
   Initializer();
};

Initializer::Initializer()
{
}

int main()
{
   Initializer init;
}

file-2.cpp:

struct Initializer
{
   Initializer();
};

Initializer::Initializer()
{
}

Command to build:

g++ file-1.cpp file-2.cpp

I get linker error message about multiple definitions of Initializer::Initializer(). Please note that the standard does not require the linker to produce this error. From section 3.2/4:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

The linker does not produce an error if the functions are defined inline:

struct Initializer
{
   Initializer() {}
};

That's OK for a simple case like this since the implementations are identical. If the inline implementations are different, the program is subject to undefined behavior.

1
Michail Angelos On

I might be a bit late for answering the question the OP made but since I think the answer is not fully clear, I would like to help future readers.

Lets try a test... compile the following files:

//main.cpp
#include <iostream>
#include "test.hpp"

class Test {
public:
     void talk() {
      std::cout<<"I'm test MAIN\n";
     }
};

int main()
{
     Test t;
     t.talk();
     testfunc();    
}

//test.hpp
void testfunc();

//test.cpp
#include <iostream>

class Test {
public:
     void talk()
      {
           std::cout<<"I'm test 2\n";
      }
};


void testfunc() {
     Test t;
     t.talk();
}

Now run the executable. You would expect to see:

I'm test MAIN
I'm test 2

What you should see thought is:

I'm test MAIN
I'm test MAIN

What happened?!?!!

Now try putting an unnamed namespace around the "Test" class in "test.cpp" like so:

#include <iostream>
#include "test.hpp"

namespace{
     class Test {
     public:
      void talk()
           {
            std::cout<<"I'm test 2\n";
           }
     };
}

void testfunc() {
     Test t;
     t.talk();
}

Compile it again and run. The output should be:

I'm test MAIN
I'm test 2

Wow! It works!


As it turns out, it is important to define classes inside unnamed namespaces so that you get the proper functionality out of them when two class names in different translation units are identical. Now as to why that is the case, I haven't done any research on it (maybe someone could help here?) and so I can't really tell you for sure. I'm answering purely from a practical standpoint.

What I would suspect though is that, while it is true that C structs are indeed local to a translation unit, they are a bit different from classes since classes in c++ usually have behavior assigned to them. Behavior means functions and as we know, functions are not local to the translation unit.

This is just my assumption.