1

I poorly understand the linkage process, and I'm struggling with multi-file compilation with template classes. I wanted to keep definitions in one file, declarations in another. But after a day of suffering, I found out that I should keep definitions and declarations in the same transition unit (see here). But my code still doesn't compile, and if I do not figure it out, I'll post another question.

But that's not the question. The question is: once I was reading about "keeping definitions and declarations in the same header" and it was considered as bad practice. One of the comments says:

The include guards protect against including the same header file multiple times in the same source file. It doesn't protect against inclusion over multiple source files

Makes sense, until I wanted to try this out with standard libraries in g++. I tried to do it with <vector>, so I have two files:

main.cpp:

#include "class.h"
#include <vector>

template class std::vector<int>;

int main()
{
    std::vector<int> arr;
}

class.h:

#include <vector>

std::vector<int> a(4);

I compiled with g++ main.cpp class.h and it compiled successfully. But why? Didn't I included <vector> twice, and therefore I have double definitions?

I checked header files on my machine, and they have definitions and declarations in the same file.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Learpcs
  • 282
  • 3
  • 10
  • That's an example of the first case described in your quote: including the same header file twice in one file (indirectly). – lurker Apr 28 '22 at 22:02
  • After the inclusion of class.h into main.cpp, the same header file is included multiple times in the same source file. – David Schwartz Apr 28 '22 at 22:05
  • Using standard headers doesn't violate the standard. The content of standard headers is up to the implementation. The ODR applies to your code, not any content of standard headers. – M.M Apr 28 '22 at 22:08
  • 4
    The One Definition Rule doesn't say you can't define something multiple times, only that all the definitions must be identical. Header files typically have include guards so even if they're included multiple times in a single translation unit, you still only get one copy. – Mark Ransom Apr 28 '22 at 22:08
  • You can include the same standard header in the same translation unit multiple times, because the standard explicitly permits this. How the implementation achieves that is not an interesting question. It does what's necessary. Possibly using include guards, possibly some other mechanism. Why do you care? ODR has nothing to do with any of this, it is about defining the same thing multiple times *in different translation units*. Templates are allowed to be defined multiple times in different translation units, as long as the definitions are exactly the same. – n. m. could be an AI Apr 28 '22 at 22:10
  • 1
    @MarkRansom Doesn't that depend on the "something" you're defining? E.g. you can't have multiple definitions of a variable, even if all those definitions are identical, but you can have multiple definitions of a class or an inline function if they all match. – Nathan Pierson Apr 28 '22 at 22:12
  • And by the way, `g++ main.cpp class.h` doesn't do what you think it does. Compiling an .h file produces a precompiled header file, not any kind of object code. It's a way to speed up future compilations. Don't muck with this unless you have a project that consists of hundreds (or millions) of source files. – n. m. could be an AI Apr 28 '22 at 22:15

1 Answers1

4

You need to differentiate between two categories of entities: Those that may have only one definition in the whole program and those that may have a single definition in each translation unit, although these definitions must be identical (i.e. same sequence of tokens plus some more requirements). In any case a single translation unit may contain only one definition of an entity.

See https://en.cppreference.com/w/cpp/language/definition for the details.

Most entities belong to the first category, but classes, templates of all kinds and inline functions belong to the second one. Not only can these entities be defined in every translation unit, but they typically need to be defined in every translation unit using them. That's why their definitions typically belong in header files, while definitions of entities of the first category never belong in a header file.

std::vector is a class template, so it and its member functions can be defined in each translation unit once.

It is not clear what you intention with the compiler invocation g++ main.cpp class.h is, but it actually compiles only one translation unit (main.cpp) and does something else for class.h because of its file ending (see comments under your question).

In the compilation unit for main.cpp you are not including two definitions of std::vector or its members, although you have two #include<vector> directives, because the standard library is required to prevent this from happening. It could e.g. use header guards as user code would to achieve the same guarantee.

If class.h would also be compiled as a translation unit, then there still wouldn't be an issue, since std::vector and its members are templated entities and so they can be defined again in this translation unit.


Note that technically the standard library is not bound by the language rules. It may not be written in C++ at all. In that case it just has to work correctly for the user, no matter how it is included, as long as the user program is in accordance with the language rules.


Also note that the explicit instantiation definition

template class std::vector<int>;

is pointless in the way you are using it. It is only required if the members of the class template are, against the typical usage mentioned above, not defined in the header but a single translation unit (but that is never the case for the standard library) or to speed up compilation time, in which case there should be an explicit instantiation declaration in the other translation units to prevent implicit instantiation.

user17732522
  • 53,019
  • 2
  • 56
  • 105