Arduino IDE: failure "undefined reference" in avr-gcc at "constexpr static" class members

First of all: I have posted my question on stackoverflow already, but I am not sure whether it will get much attention there... I think most Arduino developers use this forum, so I copied it here with little adaptions.

My code includes an enum class and a string conversion for this enum class . It compiles well under certain circumstances, but fails to compile under others; and I am not able to see how the differences are relevant.

The following code snippets are the shortest possible extract I could create, in which the failure can be reproduced.

EnumFailure.ino :

#include <Arduino.h>
#include <Streaming.h>
#include "STUC.h"

STUC* m_STUC;

void setup ()
{
  Serial.begin (9600);
  STUC::EResult result = STUC::CreateProtocolVersion (&m_STUC);
  const char* resultText = STUC::GetResultText (result);
  Serial << "Result: " << (int)result << "  " << STUC::GetResultText (result) << endl;
  // Serial << "Result: " << (int)result << "  " << resultText << endl;
  Serial << (unsigned long)m_STUC << endl;
}

void loop () { }

STUC.h:

#ifndef STUC_h
#define STUC_h

#include <Arduino.h>

#define COMMON_RESULTS \
X(None       , 1) \
X(SUCCESS    , 1)

#define COMMON_FAILURES \
X(FAIL_Pointer_Zero                ) \
\
X(FAIL_Device_Exists               ) \
X(FAIL_Device_Initialize           ) \
X(FAIL_Device_InvalidID            ) \
\
X(FAIL_Protocol_VersionInvalid     )

class STUC
{
public:
  enum class EResult : uint16_t
  {
    #define X(name,value) name = value,
    COMMON_RESULTS
    #undef X
    Dummy_FirstFailure = 0x100,
    #define X(name) name,
    COMMON_FAILURES
    #undef X
  };

  #define X(name,value) #name,
  constexpr static char const *c_EnumNames_CommonResults[]
  {
    COMMON_RESULTS
  };
  #undef X

  #define X(name) #name,
  constexpr static char const *c_EnumNames_CommonFailures[]
  {
    COMMON_FAILURES
  };
  #undef X

public:
  static EResult CreateProtocolVersion (STUC** i_ppSTUC);

  static const char* GetResultText (EResult i_Result);
};

#endif

STUC.cpp :

#include "STUC.h"

STUC::EResult STUC::CreateProtocolVersion (STUC** i_ppSTUC)
{
  if (*i_ppSTUC != 0)
    return EResult::FAIL_Device_Exists;

  *i_ppSTUC = new STUC ();
  return EResult::SUCCESS;
}

const char* STUC::GetResultText (STUC::EResult i_Result)
{
  if (i_Result < EResult::Dummy_FirstFailure)
    return c_EnumNames_CommonResults[(int)i_Result];
  return c_EnumNames_CommonFailures[((int)i_Result) - 0x100];
}

Compile log:

FQBN: arduino:avr:uno
Using board 'uno' from platform in folder: C:\Users\tk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.7
Using core 'arduino' from platform in folder: C:\Users\tk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.7

Detecting libraries used...
C:\Users\tk\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=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -IC:\Users\tk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.7\cores\arduino -IC:\Users\tk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.7\variants\standard C:\Users\tk\AppData\Local\arduino\sketches\0E962567DCB6363EE28BE6AC9462ACA9\sketch\EnumFailure.ino.cpp -o nul
Alternatives for Streaming.h: [Streaming@6.2.4]
ResolveLibrary(Streaming.h)
  -> candidates: [Streaming@6.2.4]
C:\Users\tk\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=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -IC:\Users\tk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.7\cores\arduino -IC:\Users\tk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.7\variants\standard -Id:\Arduino\libraries\Streaming\src C:\Users\tk\AppData\Local\arduino\sketches\0E962567DCB6363EE28BE6AC9462ACA9\sketch\EnumFailure.ino.cpp -o nul
Using cached library dependencies for file: C:\Users\tk\AppData\Local\arduino\sketches\0E962567DCB6363EE28BE6AC9462ACA9\sketch\STUC.cpp
Generating function prototypes...
C:\Users\tk\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=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -IC:\Users\tk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.7\cores\arduino -IC:\Users\tk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.7\variants\standard -Id:\Arduino\libraries\Streaming\src C:\Users\tk\AppData\Local\arduino\sketches\0E962567DCB6363EE28BE6AC9462ACA9\sketch\EnumFailure.ino.cpp -o C:\Users\tk\AppData\Local\Temp\1585385427\sketch_merged.cpp
C:\Users\tk\AppData\Local\Arduino15\packages\builtin\tools\ctags\5.8-arduino11/ctags -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives C:\Users\tk\AppData\Local\Temp\1585385427\sketch_merged.cpp

Compiling sketch...
"C:\\Users\\tk\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino7/bin/avr-g++" -c -g -Os -Wall -Wextra -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-IC:\\Users\\tk\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\avr\\1.8.7\\cores\\arduino" "-IC:\\Users\\tk\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\avr\\1.8.7\\variants\\standard" "-Id:\\Arduino\\libraries\\Streaming\\src" "C:\\Users\\tk\\AppData\\Local\\arduino\\sketches\\0E962567DCB6363EE28BE6AC9462ACA9\\sketch\\EnumFailure.ino.cpp" -o "C:\\Users\\tk\\AppData\\Local\\arduino\\sketches\\0E962567DCB6363EE28BE6AC9462ACA9\\sketch\\EnumFailure.ino.cpp.o"
Using previously compiled file: C:\Users\tk\AppData\Local\arduino\sketches\0E962567DCB6363EE28BE6AC9462ACA9\sketch\STUC.cpp.o
D:\Arduino\libraries\EnumFailure\EnumFailure.ino: In function 'void setup()':
D:\Arduino\libraries\EnumFailure\EnumFailure.ino:12:15: warning: unused variable 'resultText' [-Wunused-variable]
   const char* resultText = STUC::GetResultText (result);
               ^~~~~~~~~~
Compiling libraries...
Compiling library "Streaming"
Compiling core...
Using precompiled core: C:\Users\tk\AppData\Local\arduino\cores\arduino_avr_uno_43d3ab6c5d44216509e220a6b38858bc\core.a
Linking everything together...
"C:\\Users\\tk\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino7/bin/avr-gcc" -Wall -Wextra -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -mmcu=atmega328p -o "C:\\Users\\tk\\AppData\\Local\\arduino\\sketches\\0E962567DCB6363EE28BE6AC9462ACA9/EnumFailure.ino.elf" "C:\\Users\\tk\\AppData\\Local\\arduino\\sketches\\0E962567DCB6363EE28BE6AC9462ACA9\\sketch\\EnumFailure.ino.cpp.o" "C:\\Users\\tk\\AppData\\Local\\arduino\\sketches\\0E962567DCB6363EE28BE6AC9462ACA9\\sketch\\STUC.cpp.o" "C:\\Users\\tk\\AppData\\Local\\arduino\\sketches\\0E962567DCB6363EE28BE6AC9462ACA9/..\\..\\cores\\arduino_avr_uno_43d3ab6c5d44216509e220a6b38858bc\\core.a" "-LC:\\Users\\tk\\AppData\\Local\\arduino\\sketches\\0E962567DCB6363EE28BE6AC9462ACA9" -lm
C:\Users\tk\AppData\Local\Temp\ccIvWBVO.ltrans0.ltrans.o: In function `GetResultText':
D:\Arduino\libraries\EnumFailure/STUC.cpp:17: undefined reference to `STUC::c_EnumNames_CommonResults'
D:\Arduino\libraries\EnumFailure/STUC.cpp:17: undefined reference to `STUC::c_EnumNames_CommonResults'
D:\Arduino\libraries\EnumFailure/STUC.cpp:18: undefined reference to `STUC::c_EnumNames_CommonFailures'
D:\Arduino\libraries\EnumFailure/STUC.cpp:18: undefined reference to `STUC::c_EnumNames_CommonFailures'
collect2.exe: error: ld returned 1 exit status
Using library Streaming at version 6.2.4 in folder: D:\Arduino\libraries\Streaming 
exit status 1

Compilation error: exit status 1

Arduino IDE: Version: 2.3.8, Date: 2026-02-25T15:38:21.789Z, CLI Version: 1.4.1

Compile for Arduino UNO.

The weird things are:

The failure comes from the linker, not from the compiler.

if I enable the commented line in the .ino again, the compilation succeeds.

if I remove the if from CreateProtocolVersion(..), the compilation succeeds.

Where is my mistake? I don't see how the changes that let the compilation succeed are related to the failure.
Is there a mistake at all or is it a bug in the compiler (which is very unlikely, but the seem to exist sometimes)?

I already checked the differences between a successful and an aborted compilation:
If I uncomment the mentioned line in the .ino, the files in the sketches folder (C:\Users\tk\AppData\Local\arduino\sketches\) are 100% identical except this one line in .ino.cpp, but it compiles.

The files you posted are not the files referenced in the errors.

But... "undefined reference" compiler error to a defined function - #4 by PieterP

Hello. https://stackoverflow.com/questions/79928401/arduino-ide-failure-undefined-reference-in-avr-gcc

@xfpd Why do you think they aren't? The original project was broken down to these 3 files that I have copied into my post. Nothing else and nothing different is used in the compilation.

I just copied my code snippets from this post into a new sketch on a different computer with the same file names and a fresh installed Arduino IDE 2.3.8. Same result:

C:\Users\u066652\AppData\Local\Temp\ccAX03Fn.ltrans0.ltrans.o: In function `GetResultText':
D:\sw-dev_Arduino\Tests\EnumFailure/STUC.cpp:15: undefined reference to `STUC::c_EnumNames_CommonResults'
D:\sw-dev_Arduino\Tests\EnumFailure/STUC.cpp:15: undefined reference to `STUC::c_EnumNames_CommonResults'
D:\sw-dev_Arduino\Tests\EnumFailure/STUC.cpp:16: undefined reference to `STUC::c_EnumNames_CommonFailures'
D:\sw-dev_Arduino\Tests\EnumFailure/STUC.cpp:16: undefined reference to `STUC::c_EnumNames_CommonFailures'
collect2.exe: error: ld returned 1 exit status
exit status 1

Compilation error: exit status 1

I had written "STUC.c" instead of "STUC.cpp" first, which was a mistake in this post. Maybe you're referring to that.

I didn't expect it, but the issue was actually solved faster on stackoverflow (https://stackoverflow.com/q/79928401/2505186), because the problem was not related to Arduino, but to C++11.

The user JaMiT commented my post there:

Oh, you're compiling for C++11 (based on -std=gnu++11 in the compile log). Yeah, you've got an "ill-formed, no diagnostic required" situation, hence the message comes up only sometimes. (If you compile for C++17, which supports inline variables, this problem should vanish.) Let me see if I can find a duplicate.

See Strange behavior with constexpr static member variable for more information about why you don't always get an error ("no diagnostic required"; basically compiler optimizations can cause the linker to overlook the error).

The solution is adding
constexpr char const *STUC::c_EnumNames_CommonResults[];
constexpr char const *STUC::c_EnumNames_CommonFailures[];
in STUC.cpp and the failures are gone. :slight_smile:

Compile error line numbers.