Search code examples
c++c++11initializationstatic-initializationstdarray

Class with private constructor and static array of itself


Sorry if title is confusing, I couldn't find an easy way to write it in a simple sentence. Anyways, the issue I'm facing:

 // header:
class SomeThing
{
 private:
   SomeThing() {} // <- so users of this class can't come up
                  //    with non-initialized instances, but
                  //    but the implementation can.

   int some_data; // <- a few bytes of memory, the default
                  //    constructor SomeThing() doesn't initialize it
 public:
   SomeThing(blablabla ctor arguments);

   static SomeThing getThatThing(blablabla arguments);

   static void generateLookupTables();
 private:

   // declarations of lookup tables
   static std::array<SomeThing, 64> lookup_table_0;
   static SomeThing lookup_table_1[64];
};

The getThatThing function is meant to return an instance from a lookup table.

 // in the implementation file - definitions of lookup tables

 std::array<SomeThing, 64> SomeThing::lookup_table_0; // error

 SomeThing Something::lookup_table_1[64]; // <- works fine

I just can't use a std::array of Something, unless I add a public ctor SomeThing() in the class. It works fine with old-style arrays, I can define the array, and fill it up in the SomeThing::generateLookupTables() function. Apparently the type std::array<SomeThing, 64> does not have a constructor. Any ideas on how to make it work, or maybe a better structure for this concept?

============= EDIT =======

The friend std::array<SomeThing, 64> approach seems like a nice idea, but:

It is going to be used in arrays in other places as well. I would like to guarantee this class to always keep certain invariants towards to external users. With this friendly array, a user can accidentally create an uninitialised array of SomeThing.

Also, the lookup tables are generated using a rather complicated process, can't be done per inline, as in std::array<SomeThing, 64> SomeThing::lookup_table_0(some value)


Solution

  • The solution:

    std::array<SomeThing, 64> SomeThing::lookup_table_0 {{ }};
    

    Note: as explained here, {{}} is required to value-initialize the std::array without warnings in gcc. = {} and {} are correct but gcc warns anyway.

    The key to the solution is that some form of initializer must be present.


    A terminology check first: all objects are initialized in C++. There are three forms of this, default, value and zero. There are no "non-initialized" objects ; objects with no explicit initializer are called default-initialized. In some circumstances this means the member variables of the object may be indeterminate ("garbage").

    What is the problem with the no-initializer version? Firstly, the constructor for std::array<SomeThing, 64> is defined as deleted because the declaration std::array<SomeThing, 64> x; would be ill-formed (due to lack of an accessible default constructor for SomeThing, of course).

    That means that any code which attempts to use the default constructor for std::array<SomeThing, 64> is in turn ill-formed. The definition:

    std::array<SomeThing, 64> SomeThing::lookup_table_0;
    

    does attempt to use the default constructor, so it is ill-formed. However once you start introducing initializers, then the default constructor for std::array is no longer in play; since std::array is an aggregate then aggregate initialization occurs which bypasses the implicitly-generated constructor(s). (If there were any user-declared constructors then it would no longer be an aggregate).

    The version with initializers works because of [dcl.init]/13 (n3936):

    An initializer for a static member is in the scope of the member’s class

    The definition of list-initialization transforms { } to { SomeThing() } here.