compiler error with std::variant - use of deleted function error
Asked Answered
M

1

6

I have a class C that contains a struct S and a std::variant as members. The struct S has an int member a that is initialized to 0. Here is the code:

#include <variant>

class C
{
    struct S {
        int a = 0;
    };
    std::variant<S> test;
};


int main()
{
    C ctest;
    return 0;
}

When I try to compile this code with gcc 12.2.1 (also with different compilers), I get the following error:

main.cpp: In function 'int main(':
main.cpp:14:7: error: use of deleted function 'C::C()'
   14 |     C ctest;
      |       ^~~~~
main.cpp:3:7: note: 'C::C()' is implicitly deleted because the default definition would be ill-formed:
    3 | class C
      |       ^
main.cpp:3:7: error: use of deleted function 'std::variant<_Types>::variant() [with _Types = {C::S}]'
In file included from main.cpp:1:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:1402:7: note: 'std::variant<_Types>::variant() [with _Types = {C::S}]' is implicitly deleted because the default definition would be ill-formed:
 1402 |       variant() = default;
      |       ^~~~~~~
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:1402:7: error: use of deleted function 'constexpr std::_Enable_default_constructor<false, _Tag>::_Enable_default_constructor() [with _Tag = std::variant<C::S>]'
In file included from /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:38:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/enable_special_members.h:113:15: note: declared here
  113 |     constexpr _Enable_default_constructor() noexcept = delete;
      |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~

You can see the code and the error message here: https://onlinegdb.com/ZdfGp9avn

However, if I remove the default assignment =0 from the struct S, the code compiles without errors. Why is this happening? How can I fix this error without removing the default assignment? What is the difference between having a default assignment or not in this case?

Muscadel answered 18/7, 2023 at 16:6 Comment(3)
std::variant<S> test = S{}; is one option Demo.Koehler
Seems to cover the same ground as this https://mcmap.net/q/1778855/-weird-interaction-among-optional-nested-classes-and-is_constructible/817643Ramsey
godbolt.org/z/qofPGWvshHallvard
S
5

S is not default constructible until a complete-class context because the the non-static data member initializers's definitions are not available yet (only the declarations of the members).

This means when std::variant<S> tries to calculate whether S is default constructible, all the compiler has is struct S { int a = ???; }. It doesn't know whether the compiler-generated default constructor should throw because it doesn't know if a's initializer is throwing.

The fix is to specify it with a manual noexcept specifier:

class C
{
    struct S {
        int a = 0;
        S() noexcept = default;
        // Or `S() noexcept(false) = default;` if it isn't noexcept
    };
    std::variant<S> test;
};

Or to move the type out so it is complete when used:

struct S {
    int a = 0;
};

class C {
    using S = ::S;
    std::variant<S> test;
};
Sp answered 18/7, 2023 at 16:20 Comment(4)
godbolt.org/z/3f8MGK8M6Hallvard
@MarekR Thank you, I remembered the underlying reason now.Sp
Thanks a lot. The first solution works and I think I understand it. Can you please explain the second solution a bit more?Muscadel
@Muscadel It just makes S a regular type and creates a typedef inside C so you can still write C::S instead of S. You can put it in another namespace/give it a different name too if you want (e.g., namespace detail { struct internal_S_type { int a = 0; }; } class C { using S = detail::internal_S_type; std::variant<S> test; };)Sp

© 2022 - 2024 — McMap. All rights reserved.