-4

Very similar to this: C++ Initialize Member Array with Constructor Argument
But I don't use std::array, or rather, really rather not use it if there are other options.

I have this class (simplified):

template<typename T, int length> // Line 53
class StaticArray
{
    public:
        T items[length];
        StaticArray() = default;
        StaticArray(T newItems[length]) : items{newItems} { }
};

int main()
{
    StaticArray<int, 4> a;                                       // Works (uninitialized)
    StaticArray<int, 4> b = StaticArray<int, 4>();               // Works (initialized to 0)
    //StaticArray<int, 4> c = StaticArray<int, 4>({});           // Does not compile
    //StaticArray<int, 4> d = StaticArray<int, 4>({1, 2, 3, 4}); // Does not compile
    //StaticArray<int, 4> e = {1};                               // Does not compile
    return 0;
}

The lines with a and b work fine. But c does not compile:

....main.cpp:59: error: invalid conversion from ‘int*’ to ‘int’ [-fpermissive]
....main.cpp:66:51:   required from here
....main.cpp:59:49: error: invalid conversion from ‘int*’ to ‘int’ [-fpermissive]
   59 |         StaticArray(T newItems[length]) : items{newItems} { }
      |                                                 ^~~~~~~~
      |                                                 |
      |                                                 int*

Neither does d:

no matching function for call to ‘StaticArray<int, 4>::StaticArray(<brace-enclosed initializer list>)’
....main.cpp: In function ‘int main()’:
no matching function for call to ‘StaticArray<int, 4>::StaticArray(<brace-enclosed initializer list>)’
   67 |     StaticArray<int, 4> d = StaticArray<int, 4>({1, 2, 3, 4});
      |                                                             ^
....main.cpp:59:9: note: candidate: ‘StaticArray<T, length>::StaticArray(T*) [with T = int; int length = 4]’
   59 |         StaticArray(T newItems[length]) : items{newItems} { }
      |         ^~~~~~~~~~~
....main.cpp:59:23: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘int*’
   59 |         StaticArray(T newItems[length]) : items{newItems} { }
      |                     ~~^~~~~~~~~~~~~~~~
....main.cpp:58:9: note: candidate: ‘StaticArray<T, length>::StaticArray() [with T = int; int length = 4]’
   58 |         StaticArray() = default;
      |         ^~~~~~~~~~~
....main.cpp:58:9: note:   candidate expects 0 arguments, 1 provided
....main.cpp:54:7: note: candidate: ‘constexpr StaticArray<int, 4>::StaticArray(const StaticArray<int, 4>&)’
   54 | class StaticArray
      |       ^~~~~~~~~~~
....main.cpp:54:7: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘const StaticArray<int, 4>&’
....main.cpp:54:7: note: candidate: ‘constexpr StaticArray<int, 4>::StaticArray(StaticArray<int, 4>&&)’
....main.cpp:54:7: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘StaticArray<int, 4>&&’

e doesn't because StaticArray is not an "aggregate type".

I don't understand the intricacies of C++ well enough to know what's going on here. I do understand that given int a[4]; int b[4]; you can't simply do a = b;. But replacing items{newItems} in the constructor with items{1, 2, 3, 4} works fine, and so does items{{1, 2, 3, 4}}!
I suspect that the whole argument type decay thing is screwing this up.
I can remove all constructors entirely and have e work, but that then allows initialization with lists that are not of the proper size, which is quite silly.

Any pointers to workarounds? I would really rather not faff about with template magic or std::array-like kajiggery. The "purer" the better.

4
  • 3
    why don't you want to use std::array ? It solves most issues with c arrays, for example you cannot copy a c-array as a whole Commented Jan 11, 2022 at 16:27
  • 2
    your "the purer the better" concerns are a little misplaced. std::array is nothing more than what you need to avoid the issues that c-arrays have Commented Jan 11, 2022 at 16:29
  • 6
    I don't see how your implementation could be any "purer" than std::array Commented Jan 11, 2022 at 16:29
  • 2
    Do yourself a favor and learn how to use std::array. Also, templates are not magic, they are a basic part of C++. Commented Jan 11, 2022 at 16:41

1 Answer 1

0

This constructor:

template<typename... TNewItems>
StaticArray(TNewItems... newItems)
: items{newItems...}
{
    static_assert(sizeof...(newItems) == length, "Number of supplied items must match the length of the array.");
}

Use like this:

  StaticArray<int, 4> a;                                     // Works (uninitialized for int, calls the default constructor for complex types)
  StaticArray<int, 4> b = StaticArray<int, 4>();             // Works (0, 0, 0, 0)
  StaticArray<int, 4> c = StaticArray<int, 4>({});           // Weirdly works (0, 0, 0, 0; does not call the custom constructor)
  StaticArray<int, 4> d = StaticArray<int, 4>({1, 2, 3, 4}); // Works (1, 2, 3, 4)
  StaticArray<int, 4> e =                     {1, 2, 3, 4};  // Works (1, 2, 3, 4)
//StaticArray<int, 4> f = StaticArray<int, 4>({1, 2});       // Fails to compile
//StaticArray<int, 4> g =                     {1, 2};        // Fails to compile

The template-magic is reasonably minimal. Error messages for wong types (as far as C++ catches wrong types in general) in the initializer list are quite legible, even for templated classes.
The static_assert allows checking for an incorrect number of items. The error message in case of a mismatch is very legible.
The empty initializer list causing everything to be initializd to 0 is a bit weird, but not too bad.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.