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?

3

1 Answer 1

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;
};
Sign up to request clarification or add additional context in comments.

4 Comments

@MarekR Thank you, I remembered the underlying reason now.
Thanks a lot. The first solution works and I think I understand it. Can you please explain the second solution a bit more?
@Sir2B 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; };)

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.