11

Looks like I can init a POD static const member, but not other types:

struct C {
  static const int a = 42;      // OK
  static const string b = "hi"; // compile error
};

Why?

6
  • I bet someone will answer with a quote from the standard, which doesn't really tell you why :-) Commented Jul 17, 2014 at 8:44
  • 1
    I avoided the standard.
    – Bathsheba
    Commented Jul 17, 2014 at 8:44
  • T think it's because it hase to be evaluatet at compile-time. Thats why it also works with constexpr in C++11
    – TNA
    Commented Jul 17, 2014 at 8:45
  • @Bathsheba And you're also attempting to explain why, which is good. Commented Jul 17, 2014 at 8:47
  • @juanchopanza: it's not a full answer though; by no means the whole story. I might tin it.
    – Bathsheba
    Commented Jul 17, 2014 at 8:47

4 Answers 4

8

The syntax initializer in the class definition is only allowed with integral and enum types. For std::string, it must be defined outside the class definition and initialized there.

struct C {
  static const int a = 42;     
  static const string b; 
};

const string C::b = "hi"; // in one of the .cpp files

static members must be defined in one translation unit to fulfil the one definition rule. If C++ allows the definition below;

struct C {
  static const string b = "hi"; 
};

b would be defined in each translation unit that includes the header file.

C++ only allows to define const static data members of integral or enumeration type in the class declaration as a short-cut. The reason why const static data members of other types cannot be defined is that non-trivial initialization would be required (constructor needs to be called).

1
  • But the question is, why? Commented Jul 17, 2014 at 9:04
6

string is not a primitive type (like int) but is a class.

Disallowing this is sensible; the initialisation of statics happens before main. And constructors can invoke all sorts of functions that might not be available on initialisation.

4
  • that means static const char* string = "hi"; should work, right?
    – Ashalynd
    Commented Jul 17, 2014 at 8:46
  • 1
    So why is it possible to initialize static locals in a function at the moment the function is called? Commented Jul 17, 2014 at 8:46
  • 2
    That's not true- you can initialize constants at other scopes like this. It also doesn't explain other primitives like const char*.
    – Puppy
    Commented Jul 17, 2014 at 8:47
  • 4
    "Constructors can invoke all sorts of functions that might not be available on initialization"? This implies that global objects (i.e., global non-primitive variables) should also be disallowed (which, as you know, is not the case). Commented Jul 17, 2014 at 8:52
5

I'll sum up the rules about direct class initialization on C++98 vs C++11:

The following code is illegal in C++03, but works just as you expect in C++11. In C++11, you can think of it as the initializers being injected into each of the constructors of POD, unless that constructor sets another value.

struct POD {
    int integer = 42; 
    float floating_point = 4.5f;
    std::string character_string = "Hello";
};

Making the fields mutable static members will break the code in both standards, this is because the static guarantees there to be only one copy of the variable and thus we have to declare the members in a exactly one file, just like we would do with global variables using the referred using the extern keyword.

// This does not work
struct POD {
    static int integer = 42;
    static float floating_point = 4.5f;
    static std::string character_string = "Hello";
};

int   POD::integer = 42;
float POD::floating_point = 4.5f;
std::string POD::character_string = "Hello";

// This works
struct POD {
    static int integer;
    static float floating_point;
    static std::string character_string;
};

int   POD::integer = 42;
float POD::floating_point = 4.3f;
std::string POD::character_string = "hello";

If we try to make them, const static members a new array of rules arise:

struct POD {
    static const int integer = 42;               // Always works
    static constexpr float floating_point = 4.5f;    // Works in C++11 only.
    static const std::string character_string = "Hello"; // Does not work.
    constexpr static const std::string character_string = "Hello"; // Does not work (last checked in C++11)

    // Like some others have also mentioned, this works.
    static const std::string character_string;
};

// In a sourcefile:
const std::string POD::character_string = "Hello";

So, from C++11 onwards, it is allowed to make static constants of non-integer trivial types variable. Strings unfortunately does not fit the bill, therefore we cannot initialize constexpr std::strings even in C++11.

All is not lost though, as an answer to this post mentions, you could create a string class functions as a string literal.

NB! Note that you this is metaprogramming at its best, if the object is declared as constexpr static inside a class, then, as soon as you enter run-time, the object is nowhere to be found. I haven't figured out why, please feel free to comment on it.

// literal string class, adapted from: http://en.cppreference.com/w/cpp/language/constexpr
class conststr {
    const char * p;
    std::size_t sz; 
    public:
    template<std::size_t N>
        constexpr conststr(const char(&a)[N]) : p(a), sz(N-1) {}
    // constexpr functions signal errors by throwing exceptions from operator ?:
    constexpr char operator[](std::size_t n) const {
        return n < sz ? p[n] : throw std::out_of_range("");
    }   
    constexpr std::size_t size() const { return sz; }

    constexpr bool operator==(conststr rhs) {
        return compare(rhs) == 0;
    }   

    constexpr int compare(conststr rhs, int pos = 0) {
        return ( this->size() < rhs.size() ? -1 :
                    ( this->size() > rhs.size() ? 1 : 
                        ( pos == this->size()  ? 0 : 
                            ( (*this)[pos] < rhs[pos] ? -1 :
                                ( (*this)[pos] > rhs[pos] ? 1 : 
                                    compare(rhs, pos+1)
                                )   
                            )   
                        )   
                    )   
                );  
    }   

    constexpr const char * c_str() const { return p; }
};

Now you can declare a conststr directly in you class:

struct POD {
    static const int integer = 42;               // Always works
    static constexpr float floating_point = 4.5f;          // Works in C++11 only.
    static constexpr conststr character_string = "Hello"; // C++11 only, must be declared.
};  



int main() {
    POD pod; 

    // Demonstrating properties.
    constexpr conststr val = "Hello";
    static_assert(val == "Hello", "Ok, you don't see this.");
    static_assert(POD::character_string == val, "Ok");
    //static_assert(POD::character_string == "Hi", "Not ok.");
    //static_assert(POD::character_string == "hello", "Not ok.");

    constexpr int compare = val.compare("Hello");
    cout << compare << endl;

    const char * ch = val.c_str(); // OK, val.c_str() is substituted at compile time.
    cout << ch << endl;   // OK

    cout << val.c_str() << endl; // Ok

    // Now a tricky one, I haven't figured out why this one does not work:
    // cout << POD::character_string.c_str() << endl; // This fails linking.

    // This works just fine.
    constexpr conststr temp = POD::character_string;
    cout << temp.c_str() << endl;
} 
1

In C++17 :

If you can use static constexpr you will have the desired result, but if you cant : use the inline variable introduce by C++17.

In your case, since std::string does not have a constexpr constructor, the solution is inline static const std::string

Example :

#include <iostream>

int foo() { return 4;}

struct Demo
{
   inline static const int i = foo();  
   inline static const std::string str = "info";  

    
};
int main() {
    std::cout << Demo::i << " " << Demo::str<< std::endl;
    
}

Live Demo

Not the answer you're looking for? Browse other questions tagged or ask your own question.