Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Declaring an instance of a class inside this class' declaration and initializing it in-place

Tags:

c++

c++11

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?

like image 599
Violet Giraffe Avatar asked Sep 28 '22 20:09

Violet Giraffe


2 Answers

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.

like image 184
AndyG Avatar answered Oct 28 '22 09:10

AndyG


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);}
like image 1
Mike Seymour Avatar answered Oct 28 '22 09:10

Mike Seymour