0

I have a strange error returned by gcc/clang When I switch from std=c++17 to std=c++20.

struct Matrix2 {
   double ptr[9];

   // defaults
   Matrix2()                          = default; // constructor
   Matrix2(const Matrix2&)            = default; // copy constructor
   Matrix2(Matrix2&&)                 = default; // move constructor
   Matrix2& operator=(const Matrix2&) = default; // copy assignment operator
   Matrix2& operator=(Matrix2&&)      = default; // move assignment operator
   ~Matrix2()                         = default; // destructor

};

constexpr Matrix2 Id2() {
    return {   1.0   ,   0.0   ,   0.0   ,
               0.0   ,   1.0   ,   0.0   ,
               0.0   ,   0.0   ,   1.0   };
}

int main () {
   auto a = Id2();
}

with stdc++17, the code compile fine, but with stdc++20 this produce the following error : could not convert '{1.0e+0, 0.0, 0.0, 0.0, 1.0e+0, 0.0, 0.0, 0.0, 1.0e+0}' from '<brace-enclosed initializer list>' to 'Matrix2'

https://godbolt.org/z/P4afYYn9d

Does the standard now prohibit returning raw initializer_list ?? and what is the work around ??

Thx a lot

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982

1 Answers1

0

There was a change in the C++ standard between C++17 and C++20 for aggregate initialization. Have a look at aggregate initialization (cppreference)

Look at the explanation section:

  • since c++11, until c++20: no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed)
  • since C++20: no user-declared or inherited constructors

You have declared constructors in your class/struct, so as of C++20 you can't have this kind of initialization. You'd have to declare your own constructor with initialization list

[edit: adding some detailed examples]

Or remove all the defaulted constructors.

You could also have a constructor from std::array:

struct Matrix2 {
   std::array<double,9> ptr;

   // defaults
   Matrix2()                          = default; // constructor
   Matrix2(const Matrix2&)            = default; // copy constructor
   Matrix2(Matrix2&&)                 = default; // move constructor
   Matrix2& operator=(const Matrix2&) = default; // copy assignment operator
   Matrix2& operator=(Matrix2&&)      = default; // move assignment operator
   constexpr Matrix2(const std::array<double, 9> & arr)
   : ptr(arr) {}
   ~Matrix2()                         = default; // destructor    
};

constexpr Matrix2 Id2() {
    // not nice, requires extra brace.
    // not nice, potentially slower if the compiler misses
    // the opportunity to elide the copy of the array.
    return {{   1.0   ,   0.0   ,   0.0   ,
               0.0   ,   1.0   ,   0.0   ,
               0.0   ,   0.0   ,   1.0   }};
}

int main () {
   auto a = Id2();
}

Or with initialization list:

struct Matrix2 {
   double ptr[9];

   // defaults
   Matrix2()                          = default; // constructor
   Matrix2(const Matrix2&)            = default; // copy constructor
   Matrix2(Matrix2&&)                 = default; // move constructor
   Matrix2& operator=(const Matrix2&) = default; // copy assignment operator
   Matrix2& operator=(Matrix2&&)      = default; // move assignment operator
   constexpr Matrix2(const std::initializer_list<double> & init)
   :ptr{}
   {
       // not nice, this is not as good as the validation 
       // the compiler does for aggregate initialization.
       assert(std::size(init) <= std::size(ptr));
       std::copy(std::begin(init), std::end(init), ptr);
   }
   ~Matrix2()                         = default; // destructor

};

constexpr Matrix2 Id2() {
    return {   1.0   ,   0.0   ,   0.0   ,
               0.0   ,   1.0   ,   0.0   ,
               0.0   ,   0.0   ,   1.0   };
}

int main () {
   auto a = Id2();
}
Michael Veksler
  • 8,217
  • 1
  • 20
  • 33
  • Thx for the answer, so just commenting the explicitly defaulted functions fixes the problem. but this make me wonder Why this new rule was introduced, since compiler still do the same work and provide necessary implementation for c'tor, copy, move ... etc – user17071443 Jan 27 '22 at 13:10
  • @user17071443 good question, more so since it also breaks compatibility for some code. – Michael Veksler Jan 27 '22 at 13:18
  • @user17071443: Already [asked and answered](https://stackoverflow.com/q/57271400/734069) TL;DR: the C++17 wording made it almost impossible to *prevent* a type such as yours from being an aggregate if you didn't want it to be one. Now, "aggregate" is *serious* about being a type that you didn't make constructors for. – Nicol Bolas Jan 27 '22 at 14:44
  • @NicolBolas good catch. So I am voting to close this question as duplicate – Michael Veksler Jan 27 '22 at 14:48
  • thank you for the link, I will take time to understand that – user17071443 Jan 27 '22 at 15:14