-1

I would like somebody to clarify in which cases it is safe to use a static constexpr as a default argument to a class's constructor. To weed out exactly what is going on, consider the following code:

#include <array>
#include <iostream>

struct Bar {

    using Option = size_t;
    using Options = std::array<Option, 0>;
    static constexpr Option default_option = 8080;
    static constexpr Options default_options{};

    //template <typename OptionT = Options>
    Bar(
        Options options = default_options,
        Option  option  = default_option  
       ){
        std::cout << "Constructed with option " << option << std::endl;
    }
};

int main() {
    Bar bar;
}

This code seems to compile, but does not link. Specifically, when compiling with GCC 6.3, I get

prog.cc:(.text+0x13): undefined reference to `Bar::default_options'
collect2: error: ld returned 1 exit status

However, if we comment out the offending line, then the code compiles, links, and runs correctly. So presumably there is no problem using a static constexpr size_t as a default argument:

#include <array>
#include <iostream>

struct Bar {

    using Option = size_t;
    using Options = std::array<Option, 0>;
    static constexpr Option default_option = 8080;
    static constexpr Options default_options{};

    //template <typename OptionT = Options>
    Bar(
        //Options options = default_options,
        Option  option  = default_option  
       ){
        std::cout << "Constructed with option " << option << std::endl;
    }
};

int main() {
    Bar bar;
}

Can somebody explain to me why the linkage works for a size_t, but not for an array of them?

I am aware that I could define the default options inline like this:

Bar(
    Options options = std::array<Option, 0>{},
    Option  option  = default_option  
   ){
    std::cout << "Constructed with option " << option << std::endl;
}

I was just wondering if there was any other nicer fix so that the default options could easily be queried by anyone.

bremen_matt
  • 6,902
  • 7
  • 42
  • 90

1 Answers1

2

As StoryTeller points out, the first code DOES compile and link with C++17 and GCC 7.1+. To get this to compile with C++11 and older versions of GCC, you need an out-of-class declaration of the array:

#include <array>
#include <iostream>

struct Bar {

    using Option = size_t;
    using Options = std::array<Option, 0>;
    static constexpr Option default_option = 8080;
    static constexpr Options default_options{};

    //template <typename OptionT = Options>
    Bar(
        Options options = default_options,
        Option  option  = default_option  
       ){
        std::cout << "Constructed with option " << option << std::endl;
        std::cout << "Constructed with options..." << std::endl;
        for (auto & other_option : options)
            std::cout << other_option << ", ";
        std::cout << std::endl;
    }
};

// !!!! Needed for C++11 and lower gcc<7.1 versions
constexpr Bar::Options Bar::default_options;

int main() {
    Bar bar;
}
bremen_matt
  • 6,902
  • 7
  • 42
  • 90
  • 1
    Pre C++17 the line `constexpr Bar::Options Bar::default_options;` is actually the *definition*, not the *declaration*. It says to the compiler "I said that `Bar` had a member `default_options`; it belongs here". This isn't just pedantry; it means that the line cannot go in a header file that's included in multiple locations. – Daniel H Mar 27 '19 at 09:17
  • I am a little confused by that statement because in C++11, if I don't have the in-class initialization: `default_options{};`, that is, if I remove the trailing braces, then I get the error `'default_options' must have an initializer`. – bremen_matt Mar 27 '19 at 09:33
  • Perhaps a more lengthy answer would be appropriate. There seems to be a subtlety here that I am missing. – bremen_matt Mar 27 '19 at 09:35
  • I can't make a longer writeup now, and I can't find anything particularly good and not jargon-dense to link to. The gist is that `constexpr` data members worked more like other static data members, which need to be declared in the class and defined outside the class. The declaration could have the initialization in some cases (and has to for `constexpr` so the compiler can actually use it as a constant expression), but you still need the out-of-class definition. – Daniel H Mar 28 '19 at 02:05