2

I'm writing a templated container class and want constructors to differentiate between a single value to initialize all elements in the container (which is OK) and an array of values (whose size must match the size specified in the template instantiation).

Everything works great unless I specify a brace-initialized parameter of just one element for a template instance with SIZE > 1; unfortunately doing so picks the first constructor, and if the template is instantiated with SIZE > 1 but yet the user provided a brace-initialized parameter (array) of only one element, I want to catch that error with a static_assert.

Here's a stripped-down version of my code that shows the problem:

#include <iostream>

template <typename TYPE, size_t SIZE>
class Array
{
public:
   // CTOR #1: Initialize EACH ELEMENT from a single TYPE initializer
   explicit Array(TYPE const value)
   {
      std::cout << "CTOR #1, SIZE=" << SIZE << "\n";
      for (size_t i = 0; i < SIZE; ++i)
         m_buf[i] = value;
   }

   // CTOR #2: Initialize from a C-style array of the same size
   template <size_t N>
   explicit Array(TYPE const (&values)[N])
   {
      static_assert(N == SIZE, "CTOR #2: Attempt to initialize from a C-style array of the wrong size.");
      std::cout << "CTOR #2, SIZE=" << SIZE << ", N=" << N << "\n";
      for (size_t i = 0; i < SIZE; ++i)
         m_buf[i] = values[i];
   }

   template <typename T, size_t S>
   friend std::ostream& operator<<(std::ostream&, Array<T, S> const&);

private:
   TYPE m_buf[SIZE];
};

template <typename TYPE, size_t SIZE>
std::ostream& operator<<(std::ostream& os, Array<TYPE, SIZE> const& arr)
{
   for (size_t i = 0; i < SIZE; ++i)
      os << ' ' << arr.m_buf[i];
   return os;
}

int main()
{
   int CStyleArray1[1] = { 1 };
   int CStyleArray6[6] = { 1, 2, 3, 4, 5, 6 };
   int CStyleArray7[7] = { 1, 2, 3, 4, 5, 6, 7 };
   int CStyleArray8[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
   (void)CStyleArray6; (void)CStyleArray8; // avoid unused variable warnings

   Array<int, 7>  a(5);             // ctor #1

 //Array<int, 7>  b(CStyleArray6);  // ctor #2 fails due to static_assert(N==SIZE) (this is good!)
   Array<int, 7>  c(CStyleArray7);  // ctor #2
 //Array<int, 7>  d(CStyleArray8);  // ctor #2 fails due to static_assert(N==SIZE) (this is good!)

 //Array<int, 7>  e({ 1, 2, 3, 4, 5, 6});       // ctor #2 fails due to static_assert(N==SIZE) (this is good!)
   Array<int, 7>  f({ 1, 2, 3, 4, 5, 6, 7});    // ctor #2
 //Array<int, 7>  g({ 1, 2, 3, 4, 5, 6, 7, 8}); // ctor #2 fails due to static_assert(N==SIZE) (this is good!)

   Array<int, 1>  h(CStyleArray1);  // ctor #2
   Array<int, 1>  i({1});           // ctor #1 (why did this not use ctor #2 with N=1?)

 //Array<int, 7>  j(CStyleArray1);  // ctor #2 fails due to static_assert(N==SIZE) (this is good!)
   Array<int, 7>  k({1});           // ctor #1 (why did this not use ctor #2 with N=1, and fail the static_assert?)

   std::cout <<   " a:" << a
           //<< "\n b:" << b
             << "\n c:" << c
           //<< "\n d:" << d
           //<< "\n e:" << e
             << "\n f:" << f
           //<< "\n g:" << g
             << "\n h:" << h
             << "\n i:" << i
           //<< "\n j:" << j
             << "\n k:" << k
             << std::endl;
}

When I compile & run that, this is the output:

C:\GitHOME\test>g++ -O3 -std=gnu++11 -g -Wall -Werror test.cpp

C:\GitHOME\test>a.exe
CTOR #1, SIZE=7
CTOR #2, SIZE=7, N=7
CTOR #2, SIZE=7, N=7
CTOR #2, SIZE=1, N=1
CTOR #1, SIZE=1
CTOR #1, SIZE=7
 a: 5 5 5 5 5 5 5
 c: 1 2 3 4 5 6 7
 f: 1 2 3 4 5 6 7
 h: 1
 i: 1
 k: 1 1 1 1 1 1 1   <------ I expected this to use ctor #2 and fail the static_assert!

C:\GitHOME\test>

Is there any way I can get code like Array<int, 7> k({1}); to call ctor #2 so it triggers the static_assert? I'd like a brace-initialized list of one element to be considered an array of size [1], not considered a single value.

6
  • Did you try to overload the ctor for std::initializer_list<T> ?.... Ah, that won't help as std::initializer_list<T> does not have a compile time size data member Commented Jun 22, 2022 at 21:57
  • @DmytroOvdiienko If catching the mismatch with static_assert isn't possible, a runtime assert would be better than not catching the problem at all. Would std::initializer_list<T> work and let me catch the problem with a runtime assert? I've never used std::initializer_list<T> before. Commented Jun 22, 2022 at 22:06
  • 1
    {1} isn't an array. consider the line int x = { 1 }; Did i just assign an array to an int? en.cppreference.com/w/c/language/scalar_initialization Commented Jun 22, 2022 at 22:09
  • Would std::initializer_list<T> work and let me catch the problem with a runtime assert: Yes, you can call assert(std::size(l) == SIZE); Commented Jun 22, 2022 at 22:13
  • 2
    BTW, I believe it is not really good idea to have a conversion ctor with one argument. You will get exactly the same problem as vector{2} vs vector(2). The first case constructs a vector with one element of "2" while the second constructs the vector of defaulted constructed two elements. Consider adding a factory function which constructs the container and fills it with the given value Commented Jun 22, 2022 at 22:24

0

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.