-2

I am trying to get the compiler to react to some code that I believe does not violate the one-definition-rule in C++. Inside a header file, I have two declarations: one for a struct and one function, like this:

struct TestStruct {
    int a;
    double d;
};
int k();

Then I intentionally include the header file twice in another file with main() in it, to see what happens.

To my surprise, the compiler complains about multiple definitions for the struct. I was expecting the compiler not to raise any multiplicity error at all since both the struct and function have pure declarations.

It is only after I put the struct in a header-guard that the compiler stops complaining. But, there is no memory allocated for the struct. It is not a definition. Then why is the compiler mad?

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
softwarelover
  • 1,009
  • 1
  • 10
  • 22

2 Answers2

1

You can't define a struct more than once in a single translation unit.

You can define it in several translation units, but then the definitions have to be the same. (Source: cppreference/ODR).

To avoid this problem, you need to have an include guard in your header. It will silently prevent the header from being included more than once in each translation unit.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Hello Cat, could you please elaborate why the definitions have to be the same? Don't we avoid "same" definitions? The linker would complain. – softwarelover Jun 02 '19 at 22:14
  • 2
    @softwarelover The linker doesn't deal with datatypes, only with global/static variables and functions. – curiousguy Jun 02 '19 at 22:25
  • @softwarelover That's the case for (non-inline) function definitions. But being able to have several definitions for structs and classes in different TUs is important: E.g. when you `#include ` in several TUs, you effectively get `namespace std {class string {...};}` in each of them. But you don't get a linker error. – HolyBlackCat Jun 02 '19 at 22:31
  • *"why the definitions have to be the same"* Things would get weird otherwise. Consider following code: **a.cpp**: `struct A {int x;}; void foo(A); int main(){A a{1}; foo(a);}`, **b.cpp**: `#include struct B {float y;}; void foo(A a){std::cout << a.y;}` Here we have different definitions for `struct A` in different TUs. What do you think the code should do? It was decided that the answer is "it's just nonsense, it shouldn't be allowed". – HolyBlackCat Jun 02 '19 at 22:35
  • About #include you mentioned, are you sure the definition of string is included in every translation unit? Isn't it only the declaration of the string class that you get? The string definition is in the standard API that the linker links to my program. – softwarelover Jun 02 '19 at 22:43
  • 1
    @softwarelover Yes, I'm sure. You get the definition of `std::string` (i.e. `class string {...};` rather than `class string;`) (actually it's a template called `basic_string`, but let's ignore that). If a class is only declared but not defined in a TU, you can't create objects of it in that TU (e.g. `class A; A a;` doesn't compile). – HolyBlackCat Jun 02 '19 at 22:53
  • @HolyBlackCat, I understand that class A; A a; won't compile. But, doesn't the linker automatically link the standard library to the user programs? Somewhere in the standard library is the definition of string. No? – softwarelover Jun 02 '19 at 22:59
  • 1
    @softwarelover `class A; A a;` doesn't compile regardless of whether you have `class A {...};` in a different TU or not (including if it's in a linked library or not). *"linker automatically link the standard library"* Yes, but that doesn't help with classes. It lets you use functions (including member functions) that are declared (but not defined) in the standard headers. But to be able to create objects of a class, its definition has to be in the same TU (but not necessarily definitions of its member functions). – HolyBlackCat Jun 02 '19 at 23:06
  • Excellent. Yes, I was assuming the same rules for classes as functions. Got it! – softwarelover Jun 02 '19 at 23:08
0

Use include guards (or if available with your compiler) pragma once.

#ifndef PATH_TO_FILE_FILENAME_H
#define PATH_TO_FILE_FILENAME_H
struct TestStruct {
    int a;
    double d;
};
int k();
#endif

or (much better if available!)

#pragma once
struct TestStruct {
    int a;
    double d;
};
int k();

Also might be worth using namespaces to avoid polluting the global namespace

#pragma once
namespace Test
{
    struct TestStruct {
        int a;
        double d;
    };
    int k();
};

Note that to avoid muldefs you'll also need to declare k() inline should you decide to provide its definition in the header (this can be unavoidable sometimes should you need to use templates and not specify explicit template parameters).

#pragma once
namespace Test
{
    struct TestStruct {
        int a;
        double d;
    };
    template<typename T>
    inline int k<T>() // This now has to be inline or static.
    {
        // Some implementation
    }
};

Edit: On an aside, the difference between a declaration and a definition for a struct/class isn't much different from a function:

void TestFunction(); // The compiler now knows there's a function called TestFunctionand can attempt to link the symbol information to its implementation somewhere in the compilation unit.

Where in this case we're not implementing the meat and bones of the function, just saying it exists and since the compiler knows the signature (or what the function promises to take and return) it can continue happily. In TestStructs case the forward declaration (without implementation) would be

class TestStruct;