2

I have a class like this :

    class Actuator
    {
    
    public :
        enum class Action
        {
                disable,
                turn_on,
                turn_off,
                toggle
        };
        
    private:
        /*Data member*/
        
    public :
        /*function member*/
    };

In another class i define two 2D arrays of "Action" enum class :

    class Constant_Value
    {
    private:

        static constexpr std::array<std::array<Actuator::Action, NUMBER_OF_RELAYS>, NUMBER_OF_ACTUATORS> m_actuator_relay_default_config
        {{
        {{Actuator::Action::toggle, Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::disable}},
        {{Actuator::Action::disable, Actuator::Action::toggle, Actuator::Action::disable, Actuator::Action::disable}},
        {{Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::toggle, Actuator::Action::disable}},
        {{Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::toggle}}
        }};

        static constexpr std::array<std::array<Actuator::Action, NUMBER_OF_LEDS>, NUMBER_OF_ACTUATORS> m_actuator_led_default_config
        {{
        {{Actuator::Action::toggle, Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::disable}},
        {{Actuator::Action::disable, Actuator::Action::toggle, Actuator::Action::disable, Actuator::Action::disable}},
        {{Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::toggle, Actuator::Action::disable}},
        {{Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::toggle}}
        }};
        
        struct 
        {
            std::array<std::array<Actuator::Action, NUMBER_OF_RELAYS>, NUMBER_OF_ACTUATORS> actuator_relay_config;

            std::array<std::array<Actuator::Action, NUMBER_OF_RELAYS>, NUMBER_OF_ACTUATORS> actuator_led_config;
        }m_eeprom_data;

    public:
        /*Function member*/
    };

As name of arrays indicates, the two arrays are default values so i define those as "constexpr" and struct is a editable buffer. In constructure of "Consttant_Value" class I initialize the struct buffer as following :

    Constant_Value::Constant_Value()
    {

        EEPROM.begin(sizeof(m_eeprom_data));

        //Check if the EEPROM contains valid data from another run :
        if (EEPROM.percentUsed() >= 0)
        {

            //Load data from eeprom
            EEPROM.get(0, m_eeprom_data);
        }
        else
        {

            //Prepare default date to write to EEPROM :                
            m_eeprom_data.actuator_relay_config[0] = m_actuator_relay_default_config[0];
            m_eeprom_data.actuator_relay_config[1] = m_actuator_relay_default_config[1];
            m_eeprom_data.actuator_relay_config[2] = m_actuator_relay_default_config[2];
            m_eeprom_data.actuator_relay_config[3] = m_actuator_relay_default_config[3];

            m_eeprom_data.actuator_led_config[0] = m_actuator_led_default_config[0];
            m_eeprom_data.actuator_led_config[1] = m_actuator_led_default_config[1];
            m_eeprom_data.actuator_led_config[2] = m_actuator_led_default_config[2];
            m_eeprom_data.actuator_led_config[3] = m_actuator_led_default_config[3];

            // set the EEPROM data ready for writing
            EEPROM.put(0, m_eeprom_data);

            // write the data to EEPROM
            EEPROM.commit();
        }
    }

When i compile above code following error occur :

/home/ali/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp07/src/Constant_Value.cpp.o:(.text._ZN10my_program14Constant_ValueC2Ev+0x4): undefined reference to `my_program::Constant_Value::m_actuator_relay_default_config'
/home/ali/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp07/src/Constant_Value.cpp.o:(.text._ZN10my_program14Constant_ValueC2Ev+0x8): undefined reference to `my_program::Constant_Value::m_actuator_led_default_config'
collect2: error: ld returned 1 exit status
*** [.pio/build/esp07/firmware.elf] Error 1

When i define a 1D temporary array as data member of Constant_Value class like :

static constexpr std::array<Actuator::Action, NUMBER_OF_RELAYS> test
{{Actuator::Action::toggle, Actuator::Action::disable, Actuator::Action::disable, Actuator::Action::disable}};

and assign it to first element of buffer like :

m_eeprom_data.actuator_relay_config[0] = test;

and comment other assignment lines it will compile successfully.

  • Does your compiler fully support C++17 and do you use C++17 compilation flag? See [this question](https://stackoverflow.com/questions/40690260/undefined-reference-error-for-static-constexpr-member) and [this one](https://stackoverflow.com/questions/8016780/undefined-reference-to-static-constexpr-char). – Evg May 19 '21 at 07:03
  • @Evg I don't know the compiler version and not familiar with CLI interface of compiler but when move definition of arrays to .cpp file from class decleration I get : (a constexpr static data member declaration requires an in-class initializer) error and i guess declration of array is correct.However temporary "test" array is defined in same way. – ali tavakoli May 19 '21 at 07:56
  • 2
    Given the double bracket initializers I would assume C++11. I think adding the definitions such as: `constexpr std::array, NUMBER_OF_ACTUATORS> Constant_Value::m_actuator_relay_default_config;` to the .cpp files will fix the issue – MathiasJ May 19 '21 at 08:31
  • I am a couple of seconds too late but it does solve it. Check it out [here](https://wandbox.org/permlink/i1lxiqqugCFxhNhj). – 2b-t May 19 '21 at 08:32
  • @MathiasJ You are right. Your suggestion solve my problem. Now what temporary "test" array don't need additional definitions on .cpp file? – ali tavakoli May 19 '21 at 10:05
  • @2b-t Thanks. It work fine but temporary "test" array don't need additional definitions on .cpp file? There is any cpp rule about it? – ali tavakoli May 19 '21 at 10:07
  • @alitavakoli I just put the temporary `test` array there for testing purposes and forgot to delete it again. You don't need it of course. Yes, in older C++ standards there was a limitation about this: You can read about it [here](https://stackoverflow.com/questions/31531273/initializing-static-constexpr-variables-and-classes-inside-a-struct). The first comment refers to prior to C++17 and the second one adds information about C++17. Your compiler seems to be pre-C++17. If you change the compilation to C++17 on the webpage I sent you your initial version will just compile fine. – 2b-t May 19 '21 at 10:20
  • @2b-t, Please read my original post again. My own try to compile my program with "test" array without any additional definition and it was ok. What my program compiles with test but not with original arrays? – ali tavakoli May 19 '21 at 10:30
  • @alitavakoli From what i can see you declared the test array as a free variable, and not a class member. Thus the same rules does not apply. There are two things in play here, both the static keyword and the constexpr keyword, which makes a difference when they are used class members: https://en.cppreference.com/w/cpp/language/static – MathiasJ May 19 '21 at 10:32
  • @MathiasJ No, both original and test arrays are data member of "Constant_Value" class. I edit my question. – ali tavakoli May 19 '21 at 10:34
  • @alitavakoli Wait, I will write you down a more extensive answer to your question. – 2b-t May 19 '21 at 10:37
  • @alitavakoli I am sorry, then you have to read the link i provided, specifically: "or a constexpr static data member (since C++11)(until C++17) is odr-used, a definition at namespace scope is still required, but it cannot have an initializer" – MathiasJ May 19 '21 at 10:38

1 Answers1

2

Your problem is related to staticvariables. The rules for static and therefore also static constexpr variables depend on the particular C++XX standard being used. If the class members are not declared static constexpr the code will work from C++11 on-wards while with it it will only work like this for C++17 and later. For versions prior to C++17 you will have to supply additional out-of-class-definitions.


The standard for static constexpr class members has changed over the years:

  • Prior to C++17 any static class members did not have a location in memory until the variable was defined outside the class. This way one might define it at some other place, in another source file or a library. If it is used you needed to provide an out-of-class definition.

    class SomeClass {
      public:
        static int count;
    };
    
    // Out of class definition
    int SomeClass::count = 0;
    

    This also applied to constexpr class members as such class members have to be static and therefore had to be initialised.

  • In C++17 static inline class members were introduced. So somebody could define a member as inline static and provide a definition inside the class.

    class SomeClass {
      public:
        // Works since C++17
        inline static int count = 0;
    };
    

    At the same time static constexpr variables were made implicitly inline. This means any static constexpr variable would be implicitly inline static constexpr and one would have to provide a definition inside the class.


So for C++17 compilers and onwards your code is perfectly fine while with compilers prior to C++17 you would have to provide the out-of-class definitions:

constexpr std::array<std::array<Actuator::Action, NUMBER_OF_RELAYS>, NUMBER_OF_ACTUATORS> Constant_Value::m_actuator_relay_default_config;
constexpr std::array<std::array<Actuator::Action, NUMBER_OF_RELAYS>, NUMBER_OF_ACTUATORS> Constant_Value::m_actuator_led_default_config;
constexpr std::array<Actuator::Action, NUMBER_OF_RELAYS> Constant_Value::test;

Test it here.

In your case your compiler seems to be pre-C++17 and not be fully C++17 compliant (or set to C++14 or C++11): The definition of static constexpr members works with an std::array but not with an array of arrays. So something like

constexpr std::array<std::array<Actuator::Action, NUMBER_OF_RELAYS>, NUMBER_OF_ACTUATORS> Constant_Value::m_actuator_relay_default_config;
constexpr std::array<std::array<Actuator::Action, NUMBER_OF_RELAYS>, NUMBER_OF_ACTUATORS> Constant_Value::m_actuator_led_default_config;

should be sufficient with your particular compiler. This behaviour can be observed with GCC compilers compiled with C++14 but e.g. won't compile with Clang C++14. Therefore I would adise you to do so for all three of them so your code does not break with another compiler.


tl;dr: Prior to C++17 you have to proved out-of-class-definitions for all three class members m_actuator_relay_default_config, m_actuator_led_default_config and test. From C++17 onwards your code should compile fine.

2b-t
  • 2,414
  • 1
  • 10
  • 18
  • thanks. It is (In your case your compiler seems to be pre-C++17 and not be fully C++17 compliant: The definition of static constexpr members works with an std::array but not with an array of arrays.) my answer. I thought that i violated a c++ rule. – ali tavakoli May 19 '21 at 11:27
  • @alitavakoli No, it is perfectly fine for C++17. I think declaring class members as `static constexpr` can be very powerful and is quite elegant. The older C++ standards are just quite limited in this regard. But in any case adding these three lines to your code will make it compile on C++11 and C++14 as well and won't do any harm for C++17 on-wards. – 2b-t May 19 '21 at 11:30