Sorry for the generic title, but it's a mindfu*k situation, which I can't easily describe.
Suppose the following code:
struct S
{
S() = default;
int x;
int y;
};
S f()
{
return { 1, 2 };
}
This compiles and works perfectly fine. I want to forbid it, as it's bug prone (the actual code is far more complex). So, I tried adding
template<typename T>
S(std::initializer_list<T>) = delete;
but guess what - nothing changes. Tested on Visual Studio 2019 with std=c 17. The C resharper shows this as an error, but msvc actually compiles this and it works.
Wait, now it gets interesting. If S() = default;
is replaced with S() {}
, the compilation fails with
'S::S<int>(std::initializer_list<int>)': attempting to reference a deleted function
OK, this looks like something to do with user-defined constructors and initialization?! Messy, but kinda understandable.
But wait - it gets even more interesting - keeping the = default
constructor, but making the fields private
also alters this behavior and guess what - the error has nothing to do with inaccessible members, but it again shows the error from above!
So, in order to make this deletion work, I should either make the fields private or define my own empty constructor (ignore the uninitialized x and y fields, this is just a simplified example), meaning:
struct S
{
S() = default;
// S() {}
template<typename T>
S(std::initializer_list<T>) = delete;
private:
int x;
int y;
};
clang 13 and GCC 11 behave exactly the same way, while GCC 9.3 fails to compile the original code (with =default
constructor, public fields, but deleted initializer list constructor).
Any ideas what happens?
CodePudding user response:
In C 17, S
is considered an aggregate, and because of that you are not calling any constructor, you are basically directly initializing the members. If you change to using C 20, S
is no longer considered an aggregate as the rules were changes and the code will work as expected.
The reason changing the access specifier works is that the access specifier of all non-static data members of an aggregate needs to be public. Having them be non-public means your class is no longer an aggregate, and you no longer get aggregate initialization, but instead it tries to do list initialization and fails for the deleted constructor.