Search code examples
c++arduinoarduino-c++

passing static const attribute of class to a function


for an arduino school project where I draw bitmaps to a screen, I want to store said bitmaps as unsigned char arrays in classes.

Currently I'm doing it like this:

class Actor{
  public:
    static constexpr unsigned char sprite[8] = {0x97, 0xd5, 0xb7, 0x00, 0xe0, 0x4a, 0x44, 0x4a};
};

void setup() {
  Actor a;

  u8g2.begin(); // setup for display
  u8g2.drawXBM(10, 10, 8, 8, a.sprite); // draw bitmap to display buffer
  u8g2.sendBuffer(); // send buffer to display
}

which leads to the following error message:

/tmp/cc8frii8.ltrans0.ltrans.o: In function `u8g2_DrawXBM':
/home/pichler/Arduino/libraries/U8g2/src/clib/u8g2_bitmap.c:146: undefined reference to `Actor::sprite'
/home/pichler/Arduino/libraries/U8g2/src/clib/u8g2_bitmap.c:146: undefined reference to `Actor::sprite'
collect2: error: ld returned 1 exit status
exit status 1

which leads me to believe that somehow the variable "sprite" can't be accessed. Does anyone know why that could be, and how to do it right? Thanks in advance!


Solution

  • Notice, you're getting a linker error, not a compile time error. Everything is fine as far as compilation goes, but you have not done quite enough to satisfy the entire toolchain. The issue is, you never gave a definition for the variable, and the linker fails to find it when it brings everything together.

    This issue would go away if you were compiling with c++ 17. What is happening is that when you pass a.sprite to a function, the array decays to a pointer type (to the first element) and since it must have an address, is considered odr used. This means that the variable must have an unambiguous location in your application, and thus be explicitly declared (like any normal static variable that is odr-used.)

    For static members of a class, if they are odr-used, they must be declared in one place in the application, usually in a cpp file:

    class Foo {
        static constexpr int x;
    };
    
    // Foo.cpp
    #include "Foo.h"
    
    // Give a definition so it has a single location
    int Foo::x;
    

    Compile in this definition with the rest of your application and the problem will go away.

    In c++17, constexpr static members are implicitly "inline" variables, which means that a definition will be placed into every object file that sees it, in a way that does not cause duplicate definition errors. This is analogous to what "inline" means for functions. As such, from your class declaration alone it can generate the definition for you, and so you do not need the .cpp file as mentioned above if you use c++17 or later.