Struct & function declaration order problem

I'd like to get advice on the proper declaration order. IDE 2.3.4 compiling for GIGA.

I tracked down a compile problem to how I ordered the declarations of two structs and two associated functions. If I declared one struct and its associated function and then the second struct and its function (not embedded as a class, just immediately following) then in the second function declaration the compiler complains that the second struct hasn't been declared when it clearly has immediately above.

#include <string>
#include <vector>
using namespace std;

//Create a vector of structs and a compare function (for sorting), compiles OK
struct WiFiInfoRec {
  string SSID{};
  string BSSID{};
  int Channel{};
  int RSSI{};
  int EncryptionType{};
};
std::vector<WiFiInfoRec> WiFiNets;
bool CompareByRSSI(WiFiInfoRec &aParam, WiFiInfoRec &bParam) {  
  return aParam.RSSI > bParam.RSSI;
}


//Do a similar thing a second time and the compiler thinks NTPtimeRec is not declared!
struct NTPtimeRec {
  double NTPtime{};
  double RTCtime{};
  double latency{};
};
std::vector<NTPtimeRec> NTPtimesVec;
bool CompareByLatency(NTPtimeRec &aParam, NTPtimeRec &bParam) { //Compile error: 'NTPtimeRec' was not declared in this scope
  return aParam.latency > bParam.latency;
}
//Move CompareByRSSI below the NTPtimeRec declaration and both compile OK
//Comment out the definition of CompareByRSSI above and Uncomment this one instead.
// bool CompareByRSSI(WiFiInfoRec &aParam, WiFiInfoRec &bParam) {  
//   return aParam.RSSI > bParam.RSSI;
// }

void setup() {
  }

void loop() {
}

Grouping the two struct declarations together and moving the two function declarations together solves the problem, but I'd like to better understand what guideline I violated to avoid similar black holes in the future.

If you enable Show verbose output during compilation under File → Preferences you can see the file that is actually compiled. Search the output of the compiler for `.ino.cpp'. You will get a path to a temporary directory/file like below (see the end)

C:\Users\bugge\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7/bin/avr-g++ -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -flto -w -x c++ -E -CC -mmcu=atmega32u4 -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_MICRO -DARDUINO_ARCH_AVR -DUSB_VID=0x2341 -DUSB_PID=0x8037 -DUSB_MANUFACTURER="Unknown" -DUSB_PRODUCT="Arduino Micro" -IC:\Users\bugge\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino -IC:\Users\bugge\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\variants\micro C:\Users\bugge\AppData\Local\arduino\sketches\3F49D1F3E9FBCBF08B195C67020A83BC\sketch\demo.04.ino.cpp -o nul

That will show what is actually compiled and you can deduct from that why the compilation fails.

The problem is a bug in the Arduino builder (the preprocessor, not to be confused with the precompiler) drops a stitch. It's a known problem that at occasion shows its ugly face and I think that an example similar to your problem is somewhere on github (for years).

// Late edit
This is the one I was referring to; not on github (also there should be one there as well)
https://forum.arduino.cc/t/weird-g-error-compiling-simple-type-definition/680759/8

1 Like

As @sterretje said, it's not you, it's an IDE bug (they try to generate forward declarations for the functions and stuff them at the top of the file and in your example the IDE gets confused)

To avoid such issues, put your classes outside .ino files (create a .h and a .cpp for example) and follow the C++. rules for declaring stuff in the right order. Don't rely on the Arduino IDE's training wheel.

1 Like

@sterretje @J-M-L Thanks so much for the great insights. Yes I've been juggling whether to continue using the .ino approach or go all in with the .h & .cpp approach.

Most of the .ino related issues I've come across can be handled by moving the suspect declarations to the very first .ino file and avoid the forward references. But that didn't work to fix this one (I don't think it actually has a forward reference.)

I really dislike having to manage the same item in two different places (.h and .cpp) and so far I've been able to work around the Arduino .ino handling deficiencies by carefully ordering my .ino files and by moving the stubborn bits to the very first .ino file.

But yes I do feel like I'm painting myself into a corner and may soon need to try the .h and .cpp approach. (Feels like I'm jumping back 50 years in computer science technology where the human has to make the compiler's job easier rather than the other way around)

You could write an .hpp :wink:

That’s because the human tried to make the human job easier and confused the compiler :slight_smile:

What's that? New one on me.... I'll google it...

It’s a conventional extension name for stuffing together the .h and .cpp into a file and importing it.

If you have only one .ino that’s ok

However, if you use it in multiple files the. it can increase compilation time and binary size due to repeated inclusion across multiple translation units if you inline the code.

Depending on what you do you can also get linker errors from duplicate symbols.

For my scale of work any code I write would be almost inherently single inclusion.

I can easily slap an .hpp extension on my .ino files but I'm not yet understanding whether that's sufficient and how that's likely to work any better than .ino files?

I have 10-15 .ino files in my typical project, and it is sometimes hard to resolve all of the forward reference issues by reordering the .ino file names.

Yes this seems to be exactly the same issue I had. It's good to see there was some apparent ("heated") activity around this as recently as Sept 2024. Unfortunately that item is locked so I could not add my example to the long list (not that it's needed) but might have prompted another bug review.

You could have just setup() and loop() in a .ino and the rest as normal c++ files using the traditional includes etc.

Otherwise all your .ino get merged into a big file in alphabetical order and then the Arduino toolset tries to identify all the functions to generate their prototype/forward declaration at the start of this file.

The simple solution to your particular issue is to place your own function prototype after the struct has been declared.

That does get complicated if the function and struct are in separate .ino files, because the function declaration might end up before the struct declaration when the .ino files are combined.

For my immediate purposes I just added that little helper function as a public member of the struct itself since that's where it's most relevant anyway and why I had originally placed them in close proximity. That seems to work fine so far.

But it seems it's time for me to get serious about embracing something beyond just multiple .ino files. Although I have a computer science degree dating back 50 years and doing lots of low level coding along the way I'll admit that recently I've been spoiled by modern high level languages and it's painful going back down to the dirty world of C/C++ again.

I spend way more time fighting with the compiler than my algorithms. I've learned to find the "safest" approach that's most likely to work and no longer care about the memory efficiency nor performance. (I have the luxury of using a GIGA for the current project)

I'm tempted to give .hpp files a try as suggested by J-M-L but I suspect there's a reason they're not the most common approach. Either .ino or .h/.cpp seem to be more prevalent alternatives, probably for very good reasons.

After a short while you get the joy of it back :slight_smile:

Yeah - hpp are not the universal answer, a good .h and .cpp work well and are not so much work.

Remember that in C++, if you define class functions directly inside the class definition in a .hpp file, they are implicitly inline, which allows multiple inclusions across different translation units without violating the One Definition Rule (ODR), whereas if you define them in a .cpp file, they have external linkage by default and are only compiled once in that translation unit.

OK I'm trying out .h files incrementally in one of my projects.

So far if I simply rename a .ino file to .h and Include it in one of my .ino files everything still hangs together. In one case it objected to a forward reference that the Arduino IDE had apparently been working around for me. So I changed the order of that function declaration/definition ( I haven't separated them yet) and it worked ok.

But it got me trying to understand exactly how using .h files helps handle forward references and avoids the need for the Arduino IDE to assist.

Does using a .h file simply force you to figure out how to order things properly yourself since the IDE won't be doing it for you? Or does using .h files and separating declarations help handle forward references somehow? Does the compiler gather all declarations from .h files first and not care about their order?

I'm also wondering if each .cpp file results in a separate compilation and only cares about the order of its own functions? And if my current setup (no .cpp files, some .h files with code and some .ino files) is still all just one big compilation unit and doesn't help avoid forward reference issues.

Anyway I'm trying to better understand the ramifications of using .h and .cpp files and what's my best strategy for modularizing a single growing Arduino sketch with minimal interest in creating reusable libraries (yet).

My main goals are to avoid the Arduino bug that started this thread and also reduce the hassle of keeping the .ino tabs in the right alphabetical order to avoid forward reference errors. I just want a more robust modular single project sketch.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.