5

This is what I have now:

class CColorf
{
public:
    CColorf();
    CColorf(float r, float g, float b, float a = 1.0f);

public:
    float r, g, b, a;

// predefined colors
    // rgb(0.0, 0.0, 1.0)
    static const CColorf blue;
};

It works with blue defined in ccolorf.cpp like so:

CColorf const CColorf::blue = CColorf(0.0f, 0.0f, 1.0f);

And this is what I would like to do:

class CColorf
{
    ...

// predefined colors
    // rgb(0.0, 0.0, 1.0)
    static const CColorf blue = CColorf(0.0f, 0.0f, 1.0f);
};

But it produces a compilation error:

a static data member with an in-class initializer must have non-volatile const integral type

Is there a way to avoid the need for separate declaration and definition here?

Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335
  • 2
    Declare it `constexpr`. `CColof` is a literal type, so that should then work. – Columbo Feb 05 '15 at 14:56
  • @Columbo: isn't `constexpr` supposed to be used with expressions (or functions), not declarations? – Violet Giraffe Feb 05 '15 at 14:57
  • 1
    ... don't know what you mean, but you sound confused. `constexpr` is a decl-specifier (declaration specifier) and thus can only be used in declarations. – Columbo Feb 05 '15 at 14:59
  • @Columbo: I am confused. Never used `constexpr` before, and barely know what it means. Reading on it right now. – Violet Giraffe Feb 05 '15 at 15:00
  • Actually, this seems not to work anyway because `CColorf` is not complete at the point of declaration. Nevermind. – Columbo Feb 05 '15 at 15:00
  • @Columbo: yep. Exactly that. – Violet Giraffe Feb 05 '15 at 15:01
  • What if you use list-initialization instead of copying a temporary? Maybe that temporary is the problem? – Columbo Feb 05 '15 at 15:06
  • 1
    @Columbo: No, the problems are trying to initialise an incomplete type, and trying to initialise a static member of non-literal type in-class. There's no getting round those restrictions (This class could be made literal by removing the constructors (making it an aggregate), or declaring them `constexpr`. But it would still be incomplete within its definition.) – Mike Seymour Feb 05 '15 at 15:15
  • @AndyG: Yes you can, if they're constant, and a complete literal type (or, before C++11, an integer type). The problems are that this is neither complete nor literal. – Mike Seymour Feb 05 '15 at 22:09

2 Answers2

3

The rule of thumb here is that you cannot use in-class member initialization of a member variable if it's static (and not also const int), however there are some exceptions (just none that apply to your case).

In the C++98 standard, you could only member initialize static const int

In C++11 standard, you can member initialize everything except static (with exception to the C++98 standard).

You could get around this if your static member was constexpr:

§ 9.4.2 (November 2014 draft)

If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression (5.20). A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. — end note ] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.

To explain this snippet a little more clearly: If you want to try to get around things with constexpr, your type must be "literal".

A literal type (§ 3.9.10):

  • Has a "trivial" destructor
  • Has only constant expression constructors
  • Has only literal type base classes and data members
  • Or is an aggregate type
  • Or is void, scalar (e.g., int), a reference, or array of literal types

A destructor is "trivial" if:

  • It's compiler-generated (i.e. you didn't define one)
  • And each non-static member object has a trivial destructor

Given all of this, you might take a look at your code and think "Hm, well I could make all my constructors constexpr, and then change static const CColorf blue to static constexpr CColorf blue and I'm good."

However, your class is "incomplete" at the time you declare your static. Let's think about the following example:

class A{
    private:
        A member;
}

Every instance of A now has an instance of A. How many bytes does the compiler allocate for A? It can't tell. Infinitely many, perhaps, due to the recursion. A is incomplete inside it's own class. You have a similar problem of incompleteness. However, let's make it a pointer instead:

class A{
    private:
        A* member;
}

Now it's easy because A* is a pointer type, which the compiler knows the size of.

So now you think "Okay, I'll just make static constexpr CColorf blue a pointer like static constexpr CColorf* blue = new CColorf(0.0f, 0.0f, 1.0f);

But you can't, because the new operator is not constexpr.

And you can't try const because we already went over why.

So maybe you think about overloading the new operator to be constexpr, but you can't do that either.

So you're out of luck.

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • See Mike Seymour's comment to the question. What you're suggesting is _not_ an answer. – Violet Giraffe Feb 05 '15 at 22:06
  • An answer to my question is either a piece of code showing how to do what I want, or 3 words: "This is impossible". Your "answer" is neither; it says "you could get around this if your static member was `constexpr`", but making my static members `constexpr`, in fact, doesn't solve the problem. Not to mention that piece of of non-human-readable text from the standard hardly tells me anything. – Violet Giraffe Feb 06 '15 at 05:41
  • @VioletGiraffe: I apologize. Sometimes things clear to one person may not be so clear to another. I've added more detail to my answer, which I hope makes things more clear. – AndyG Feb 06 '15 at 14:04
1

You can't do that.

The error message implies that you're compiling as C++03, where only constant static members of integral type can be initialised in their declaration; so you can't do this for any class type.

C++11 relaxes the rules, but there are still restrictions:

  • the type must be literal. You could make this type literal by making the constructors constexpr; but
  • the type must be complete, and a class isn't complete within its definition (except inside member definitions)
  • the member mustn't be odr-used; that is, you can only use it as an rvalue expression, and can't take its address or create a reference to it.

While the first point can be fixed, and the third would just restrict what you can do with the member, not whether you can define it, the second makes it impossible. You'll have to define the variable in the usual way, outside the class in a single translation unit.

If you want to keep everything in the class definition, and the value available to help compile-time optimisation, you could define a function rather than a variable

static CColorf blue() {return CColorf(0.0f, 0.0f, 1.0f);}
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644