Weird g++ error compiling simple type definition

I'm conversant with C++ and I've used g++ on handfuls of platforms. The g++ in the Arduino IDE has a weird bug I haven't seen elsewhere.

This code compiles fine:

#include <SPI.h> //to drive the dotstar strips
#include <Adafruit_FreeTouch.h> //touch sensing
#include <cassert>
#include <cstdint>

#define DEBUGGING //

#define NUMOF(x) (sizeof (x) / sizeof (x[0]))

enum DeviceState : uint8_t {
  S_STANDBY, S_STANDBY_TRIGGERED, S_ARM, S_RAMPUP, S_FIRING, 
  S_RAMPDOWN
};
static DeviceState state;

static bool looping = false; //so we don't call serial from setup()
static void debugPrint(const char* tx)
{
#ifdef DEBUGGING
  //Make sure we don't call Serial.print until loop() happens.
  //It seems to make a mess when called from setup().
  if (looping)
  {
    Serial.print(tx);
  }
#endif  
}
//...followed by many uses of DeviceState

But if I move the enum declaration to after the definition of debugPrint, all subsequent uses of DeviceState come up as undefined. This is true even if I change the name of DeviceState to something else, or make it into a typedef uint8_t DeviceState; and use #define or static const to create the S_ constants. In short, this fails:

#include <SPI.h> //to drive the dotstar strips
#include <Adafruit_FreeTouch.h> //touch sensing
#include <cassert>
#include <cstdint>

#define DEBUGGING //

#define NUMOF(x) (sizeof (x) / sizeof (x[0]))

static bool looping = false; //so we don't call serial from setup()
static void debugPrint(const char* tx)
{
#ifdef DEBUGGING
  //Make sure we don't call Serial.print until loop() happens.
  //It seems to make a mess when called from setup().
  if (looping)
  {
    Serial.print(tx);
  }
#endif  
}

enum DeviceState : uint8_t {
  S_STANDBY, S_STANDBY_TRIGGERED, S_ARM, S_RAMPUP, S_FIRING, 
  S_RAMPDOWN
};
static DeviceState state; //no error on this line...
//...followed by many uses of DeviceState, all of which claim that DeviceState does not name a type

There's certainly nothing in C++ that demands all enums, typedefs etc be placed before functions. Obviously I can live with the enum declared earlier, but I'm fussy about my code and I generally define things in the order of need, and debugPrint() doesn't need DeviceState so it should be above DeviceState's definition. My concern, obviously, is if something as simple as declaring a type can break g++, there's something very wrong with the compiler and if you can't trust a compiler you shouldn't let it compile code that controls hardware.

Anyone seen anything like this? Any known fix? I'm up to date on Arduino IDE and am on Linux Mint.

Are you passing DeviceState as a function argument? I can't tell since you left out all of the parts that show an error.

The auto-generated function prototypes (a feature of the Arduino IDE) are being placed before the declaration of the enum so the type is not defined at that point. You can work around the problem by using arguments of type "enum DeviceState instead of "DeviceState". Or you can add your own function prototypes AFTER the declaration of the enum.

Wow. Thank you. That's exactly where the errors were being declared. I thought I was programming in straight C++, but clearly there's some magical extra preprocessing rules.

scottmayo:
clearly there's some magical extra preprocessing rules

Yes. And sometimes the tool that implements the rules breaks things. This is part of the reason why I generally use an IDE other than the Arduino IDE.

The Arduino IDE is helpful for beginners. Someone who makes use of advanced features (e.g., enum) should proceed with caution.

You can find what is actually compiled by enabling verbose output during compliation in file -> preferences.

In the output (below for a Windows system), you will find something like

"[color=teal][b]C:\Users\sterretje\AppData\Local\Temp\arduino_build_399817[/b][/color]\core\new.cpp.o"

In that directory is a sketch directory that contains the actual file(s) that is (are) compiled (e.g. sketch_nov01a.ino.cpp).

I thought I was programming in straight C++,

Did the apparent lack of a main() function not ring any alarm bells ?

The Arduino IDE’s build routine, which creates these function prototypes, does appear to be getting better but obviously does not yet catch all cases and can put the prototype before the point where data types it may use are declared.
So, this must be regarded as a bug and should be reported, in the hope that in a future release at least the specific instance of the problem exhibited here will be solved.
Or have the IDE developers signaled that they are not going to give further attention to this ?
Of course, I also understand it is frustrating for those who have been caught by this and, having found a work around, just want to move on without further effort in opening an incident.

Edit 1

. . . and usually, it is the more experienced users who get caught, simply because they are more likely to be using constructs which are not correctly handled. Further, it is not transparent to the user that his/her code has been "augmented" by this special feature, so the error messages are completely misleading.

OK. Rather than just talk about it, I've created a simply example which duplicates the problem, based on an earlier attempt of mine to get this started:

// Arduino IDE 1.8.13


enum class StateA { one, two, three } ;
StateA stateA ;

void setStateA( StateA newState ) {
  stateA = newState ;
}

enum class StateB { one, two, three } ;
StateB stateB ;

void setStateB( StateB newState ) {
  stateB = newState ;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin( 115200 ) ;
  setStateA( StateA::one ) ;
  setStateB( StateB::one ) ;
}

void loop() {
}

Error Report:

sketch_oct10a:11:17: error: variable or field 'setStateB' declared void
 void setStateB( StateB newState ) {
                 ^~~~~~
sketch_oct10a:11:17: error: 'StateB' was not declared in this scope
C:\Users\6V6GT\Documents\Arduino\sketch_oct10a\sketch_oct10a.ino:11:17: note: suggested alternative: 'StateA'
 void setStateB( StateB newState ) {
                 ^~~~~~
                 StateA
exit status 1
variable or field 'setStateB' declared void

Output of build routine including auto generated prototypes in file .ino.cpp :

#include <Arduino.h>
#line 1 "C:\\Users\\6V6GT\\Documents\\Arduino\\sketch_oct10a\\sketch_oct10a.ino"
enum class StateA { one, two, three } ;
StateA stateA ;

#line 4 "C:\\Users\\6V6GT\\Documents\\Arduino\\sketch_oct10a\\sketch_oct10a.ino"
void setStateA( StateA newState );
#line 11 "C:\\Users\\6V6GT\\Documents\\Arduino\\sketch_oct10a\\sketch_oct10a.ino"
void setStateB( StateB newState );
#line 15 "C:\\Users\\6V6GT\\Documents\\Arduino\\sketch_oct10a\\sketch_oct10a.ino"
void setup();
#line 22 "C:\\Users\\6V6GT\\Documents\\Arduino\\sketch_oct10a\\sketch_oct10a.ino"
void loop();
#line 4 "C:\\Users\\6V6GT\\Documents\\Arduino\\sketch_oct10a\\sketch_oct10a.ino"
void setStateA( StateA newState ) {
  stateA = newState ;
}

enum class StateB { one, two, three } ;
StateB stateB ;

void setStateB( StateB newState ) {
  stateB = newState ;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin( 115200 ) ;
  setStateA( StateA::one ) ;
  setStateB( StateB::one ) ;
}

void loop() {
}

Clear is that an unwanted function prototype setStateB( StateB xxx) has been created before the declaration of StateB .

I'll see how far I get with a bug report.

Edit 2

Bug report: Incorrect placement of auto-generated prototypes gives misleading error messages · Issue #362 · arduino/arduino-builder · GitHub

johnwasser:
. . .
The auto-generated function prototypes (a feature of the Arduino IDE) are being placed before the declaration of the enum so the type is not defined at that point. You can work around the problem by using arguments of type "enum DeviceState instead of "DeviceState". Or you can add your own function prototypes AFTER the declaration of the enum.

Indeed, adding your own prototype does appear to work, in that it seems to prevent the auto generation of one, at least in the cases I tried. However, the prototype you create must have an exact copy of the argument list of the original function, otherwise it still goes ahead and creates another one. This is much stricter than the normal C++ rules for function prototypes, where it is not even necessary to have a variable name following the type declaration.