Skip to content

<variant>: clang-cl considers std::variant not constexpr for certain situations when non-trivially destructible types are involved #4901

@jk-jeon

Description

@jk-jeon

Please see the following:

#include <variant>

struct X {
    constexpr ~X() {}
};

struct Y {
    X x;
};

struct Z1 {
    std::variant<Y, int> z;
};

struct Z2 {
    std::variant<int, Y> z;
};

constexpr auto z11 = Z1{0}.z.index();
constexpr auto z12 = Z1{Y{}}.z.index();
constexpr auto z21 = Z2{0}.z.index();
constexpr auto z22 = Z2{Y{}}.z.index();

cl compiles the above just fine under /std:c++20 and /std:c++latest, and I believe this should work fine under C++20 and above. Recent versions of gcc and clang both also work fine with their default standard library: https://godbolt.org/z/YhfW8M9b6.

But clang-cl (with MS STL) rejects this code, saying that z11 and z12 cannot be initialized by constant expressions:

C:\test>clang-cl /EHsc /std:c++20 .\repro.cpp
.\repro.cpp(19,16): error: constexpr variable 'z11' must be initialized by a constant expression
   19 | constexpr auto z11 = Z1{0}.z.index();
      |                ^     ~~~~~~~~~~~~~~~
.\repro.cpp(19,22): note: subexpression not valid in a constant expression
   19 | constexpr auto z11 = Z1{0}.z.index();
      |                      ^
.\repro.cpp(19,22): note: in call to 'Z1{0}.z.~_Variant_storage_()'
.\repro.cpp(19,22): note: in call to 'Z1{0}.z.~_Variant_base()'
.\repro.cpp(19,22): note: in call to 'Z1{0}.z.~_Variant_destroy_layer_()'
.\repro.cpp(19,22): note: in call to 'Z1{0}.z.~_Non_trivial_copy()'
.\repro.cpp(19,22): note: in call to 'Z1{0}.z.~_Non_trivial_move()'
.\repro.cpp(19,22): note: in call to 'Z1{0}.z.~_Non_trivial_copy_assign()'
.\repro.cpp(19,22): note: in call to 'Z1{0}.z.~_Non_trivial_move_assign()'
.\repro.cpp(19,22): note: in call to 'Z1{0}.z.~variant()'
.\repro.cpp(19,22): note: in call to 'Z1{0}.~Z1()'
.\repro.cpp(20,16): error: constexpr variable 'z12' must be initialized by a constant expression
   20 | constexpr auto z12 = Z1{Y{}}.z.index();
      |                ^     ~~~~~~~~~~~~~~~~~
.\repro.cpp(20,22): note: subexpression not valid in a constant expression
   20 | constexpr auto z12 = Z1{Y{}}.z.index();
      |                      ^
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.z.~_Variant_storage_()'
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.z.~_Variant_base()'
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.z.~_Variant_destroy_layer_()'
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.z.~_Non_trivial_copy()'
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.z.~_Non_trivial_move()'
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.z.~_Non_trivial_copy_assign()'
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.z.~_Non_trivial_move_assign()'
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.z.~variant()'
.\repro.cpp(20,22): note: in call to 'Z1{Y{}}.~Z1()'
2 errors generated.

STL version

Microsoft Visual Studio Community 2022
Version 17.11.0

But I believe the issue persists in the most recent version too.

Additional context

In my real code, X is std::vector. std::variant works just fine if std::vector is directly in the list of alternatives, but it fails if one of the alternatives contains std::vector as a subobject.

If I comment out z11 and z12, then now it compiles fine. Or if I explicitly default the destructor of Y, then it now accepts the code.

Another funny thing is that this issue appears only if std::variant contains both a trivially destructible type and a non-trivially destructible type. Like, if I replace int by X then now it works fine.

I believe this is a clang bug not MS STL's fault, but will it make sense if MS STL can provide a workaround? Even if you don't want to fix this from your side, I thought it would be helpful for you to be aware of the issue.

Not sure if this is related to one of the issues you are tracking: llvm/llvm-project#59854.

Note: I did not make a bug report to LLVM because I'm not sure what exactly is causing this. As I said clang works with both libstdc++ and libc++ just fine (only with the most recent version though; it seems older versions still use placement new so it doesn't work but for a totally different reason), but I couldn't spot what exactly is different between libc++/libstdc++ and MS STL.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfixedSomething works now, yay!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions