-1

EDIT #1: Added minimal repex, see below:

Background

I'm trying to segment my code into multiple .cpp files, all of which should have access to an enum type, which I thought I was declaring in a header file, then including in a number of cpp files.

However, when I try linking them together, I either get multiple definition or not defined errors. I think this stems from my probable misunderstanding of what a declaration is, and what a definition is. I mean I'm clear on this (reference):

  • A variable is defined when the compiler allocates the storage for the variable.
  • A variable is declared when the compiler is informed that a variable exists (and this is its type); it does not allocate the storage for the variable at that point.

Question

If I have this in my main.h file, this is clearly just a declaration, isn't it?

#1

enum operation_status {
    PRE_START,
    RUNNING,
    PAUSED
};

How about this one then? Is this a declaration, or a definition?

#2

operation_status op_status;

I would think that this is indeed a declaration, and the definition to go with it would be

#3

op_status = PRE_START;

Thank you in advance for you answers!

Reproducible example:

Main.h

enum operation_status {
    PRE_START,
    RUNNING,
    PAUSED
};

//error given by this: 'multiple definition of op_status'
operation_status op_status;

//error given by this: 'undefined reference to op_status'
extern operation_status op_status;

void changeStatus();

Main.cpp

#include <Arduino.h>
#include <main.h>

void setup() {
    op_status = PRE_START;
}

void loop() {
    ;
}

Change.cpp

#include <main.h>

void changeStatus() {
    op_status = RUNNING;
}

What would be the best solution to get around this? Thank you!

Community
  • 1
  • 1
pfabri
  • 885
  • 1
  • 9
  • 25

2 Answers2

3

First off, every definition is a declaration; a definition is a special kind of declaration.

enum operation_status {
    PRE_START,
    RUNNING,
    PAUSED
};

is technically a definition - it defines the type operation_status. But it's a definition of the sort that's allowed to appear in multiple compilation units (as long as it has the same contents and meaning every time), so a header file is usually the right place for it.

operation_status op_status;

is in fact a definition, even though it has no initializer. To make it not a definition, you would need the extern keyword:

extern operation_status op_status;

The definition to go with that belongs in one source file and may optionally have an initializer.

operation_status op_status = PRE_START;

Finally,

op_status = PRE_START;

is not a declaration at all. It's a statement, and is only valid inside a function definition.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Your explanation is very clear, thank you! Adding the `extern` keyword to `main.h` and then adding `operation_status op_status` to `main.cpp` does indeed solve the issue. Which is great. What is not so great, is that this is exactly what I wanted to avoid, i.e. I wanted to keep in `main.h` as much as possible, to avoid clutter in `main.cpp`. Having to use an `extern` declaration in `main.h` followed by an uninitialised definition in `main.cpp` makes things more cluttered. – pfabri Jan 16 '17 at 00:06
  • What I find confusing is this: `int x` would always be seen as a *declaration*, only writing `int x = 0` would be a *definition*. Then why is `operation_status op_status` a *definition* and not just a *declaration* if I don't initialise it? Is this because `operation_status` has its list of possible values? – pfabri Jan 16 '17 at 10:23
  • The first is not a _definition_. It is a type _declaration_. – too honest for this site Jan 16 '17 at 13:22
  • `int x;` is also a definition. – aschepler Jan 16 '17 at 14:44
  • @Olaf: See Standard 3.1/2. In the example, `enum { up, down };` is called a definition. – aschepler Jan 16 '17 at 14:45
  • @aschepler: Ok, sorry, I assumed it was the same as in C. – too honest for this site Jan 16 '17 at 14:57
1

I'm trying to segment my code into multiple .cpp files, all of which should have access to an enum type, which I thought I was declaring in a header file, then including in a number of cpp files.

You cannot forward-declare an unscoped enumeration which has no specified underlying type (standard does not allow this). This is a kind of enum from pre c++11 age. For such enums, Standard does not enforce on compiler any default underlying type, so if you specify only few values in your enum it can use a char, or if you have some large values in might use short or int. But this will be known only when you define it.

If you want declare an unscoped enum, then you must specify its underlying type. This is possible since c++11:

// forward declare (in header file .h)
enum operation_status : int;


// define (in implementation file .cpp)
enum operation_status : int {
    PRE_START,
    RUNNING,
    PAUSED
};

but you can also go ahead and use new scoped enums, their underlying type by default is int:

// forward declare
enum class operation_status;


// define
enum class operation_status {
    PRE_START,
    RUNNING,
    PAUSED
};

[edit]

Your questions:

enum operation_status {
    PRE_START,
    RUNNING,
    PAUSED
};
If I have this in my main.h file, this is clearly just a declaration, isn't it?

this is both declaration and definition, my above answer goes deeper on this.

operation_status op_status;

thats definition. You dont need to initialize it, to make it a definition.

marcinj
  • 48,511
  • 9
  • 79
  • 100
  • Thank you, your answer is informative. The Arduino environment uses a strange C/C++ hybrid. In C what you call unscoped is fine, it's always an `int` type. But I honestly don't know if I should be using the C or the C++ convention here. It's never clear which side of the C/C++ fence I'm on. – pfabri Jan 16 '17 at 08:26