8

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)

M.M
  • 130,300
  • 18
  • 171
  • 314
Gábor Buella
  • 1,632
  • 13
  • 19
  • Consider using `std::vector` if your class is movable. If it's not movable, I think `std::deque` still works, provided you emplace, of course. – Brian Bi Dec 30 '14 at 01:11
  • I wonder if this class definition might be ill-formed, standard containers should only be instantiated with a complete type. (And this problem only arises due to class containing a static member which comes from a template of itself) – M.M Dec 30 '14 at 02:26
  • Matt: that standard container is not instatiated yet in the header, but only in the implementation. – Gábor Buella Dec 30 '14 at 02:38
  • This question is interesting as it is a case where C-style array cannot be so simply replaced by `std::array` – M.M Dec 30 '14 at 02:57
  • I think I have another semi solution: if C++ works the same way as C in this regard, global variables, such as Something::lookup_table_0 ( which is not in the stack of any function, so global ) are initialized to zeroed out memory. So if I make a private constructor that just sets `some_data` to zero, and use that to value-initialize the std::array, that is essentially the same thing. But it is still a hack. – Gábor Buella Dec 30 '14 at 03:06

3 Answers3

5

The std::array<SomeThing, 64> class clearly doesn't have access to the private default constructor when it tries to define the instance. You can give it the necessary access by adding

friend class std::array<SomeThing, 64>;

to the definition of SomeThing.

Dietmar Kühl
  • 141,209
  • 12
  • 196
  • 356
4

As your constructor is private, std::array can't use it.

You may add friend class std::array<SomeThing, 64>; in SomeThing to give access to the constructor.

An alternative is to use the available public constructor to initialize the element of array:

std::array<SomeThing, 64> SomeThing::lookup_table_0{
    SomeThing(blablabla_ctor_arguments), ..
};

EDIT:

You can even do, if you have your move or copy constructor available:

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

to have your whole array default initialized.

Jarod42
  • 173,454
  • 13
  • 146
  • 250
  • Thx, but as I see, that would expose the private constructor. See my EDIT in the question. – Gábor Buella Dec 30 '14 at 01:04
  • @BuellaGábor: It seems that I have found the magic solution :) – Jarod42 Dec 30 '14 at 01:45
  • Wow, that is clever. I think, it actually creates an instance, leaves it non-initialized with garbage memory, and makes another 63 copies of that garbage memory. Which is kind of stupid, but well, technically "works". – Gábor Buella Dec 30 '14 at 01:51
  • @BuellaGábor `SomeThing()` calls the default constructor, there is no "non-initialized with garbage memory" – M.M Dec 30 '14 at 01:57
  • @MattMcNabb : look at the constructor in the question: it does nothing. Meanwhile, the class does actually have member variable ( not mentioned in the question, but it does have ). That member had no constructor, so it is just "garbage" memory as I see it. Actually, I originally wanted 64 instances on garbage memory, i.e. uninitalized memory. – Gábor Buella Dec 30 '14 at 02:00
  • @MattMcNabb I added a member variable in the code. That int is the garbage memory I refer to. – Gábor Buella Dec 30 '14 at 02:03
  • @BuellaGábor OK I see what you mean. The objects are considered to be initialized even though the `int` in them is not. – M.M Dec 30 '14 at 02:04
  • @MattMcNabb one last comment, since the constructor is called 64 times, I guess there is no copying, so there should be no undefined behaviour. Even if it would be copied, it would not be UB, unless that uninitialized variable is actually used before initialisation. – Gábor Buella Dec 30 '14 at 03:36
  • @BuellaGábor You're right, it must do 64 default initializations (unlike `vector` for example where it has the choice of doing all defaults, or one default plus copies) – M.M Dec 30 '14 at 03:41
4

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.

Community
  • 1
  • 1
M.M
  • 130,300
  • 18
  • 171
  • 314
  • For me, this results in: " error: missing initializer for member 'std::array::_M_elems' [-Werror=missing-field-initializers] " – Gábor Buella Dec 30 '14 at 01:47
  • That is essentially the same as the other solution `lookup_table_0( SomeThing() }` . There is a difference between non-initialized, and value-initialized. Anyways, right now I'm thinking I will stay with old-style arrays. – Gábor Buella Dec 30 '14 at 02:08
  • After some thinking, I think I can accept this one as a solution, expecting the compilers optimiser to recognise there is no work to do anyways. BTW, I just tried this constructor: `SomeThing() { std::cout << "ctor here\n"; }` It prints "ctor here\n" 64 times with your example `lookup_table_0 {{ }}` but prints it 64 times with a C-style array as well. I was originally looking for the concept of "a chunk of memory with size 64*sizeof(SomeThing)", without running a constructor 64 times, but I guess the result is the same with any decent compiler anyways. – Gábor Buella Dec 30 '14 at 03:28
  • OK. You could allocate a chunk of memory and use placement new on it when you're ready but that is a lot more mess. If your default ctor actually does nothing it'll be optimized out anyway. – M.M Dec 30 '14 at 03:35