jeudi 11 novembre 2021

Attempting to delete an initializer list constructor does not always take effect

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?

Aucun commentaire:

Enregistrer un commentaire