4

I ran across a compile error in clang with this code. I don't see any problem with it and it works in msvc and gcc. Am I missing something or is it a clang bug?

#include <iostream>
#include <string>
#include <type_traits>

constexpr bool do_tests()
{
    // prints error message at runtime if test is false
    auto verify = [](bool test, std::string message = "error") {
        if (!std::is_constant_evaluated() && !test)
            std::cout << message << '\n';
        return test;
    };
    return verify(1 == 1, "test");
};


int main()
{
    constexpr bool b = do_tests();
    std::cout << b << '\n';
}

compiler explorer

Inexplicable error message from clang:

basic_string.h:356:10: note: assignment to member '_M_local_buf' of union with no active member is not allowed in a constant expression

doug
  • 3,840
  • 1
  • 14
  • 18
  • Related:https://stackoverflow.com/questions/63753257/error-in-accessing-a-union-member-in-constant-expression – Jason Sep 12 '22 at 03:36
  • @JasonLiam The code doesn't involve unions except in the weird error message inside the string lib code. Also, this is in c++20. – doug Sep 12 '22 at 03:49
  • Yes, that is why i didn't close it and said that is is related(but not a dupe). – Jason Sep 12 '22 at 03:50

1 Answers1

7

There is no problem with the code and Clang also compiles it correctly if you use libc++ (-stdlib=libc++). Compiler explorer by default uses libstdc++ for Clang (-stdlib=libstdc++). There simply seems to be a compatibility issue between the two. My guess is that libstdc++ is implementing the constexpr string in a way that is technically not allowed in constant expressions, but GCC tends to be less strict in that regard than Clang is, so that Clang fails with the implementation while GCC doesn't have a problem with it.


To be more precise in libstdc++ the implementation of std::string contains an anonymous union with one member being a _CharT array named _M_local_buf. Then libstdc++ is doing the following if the evaluation happens in a constant expression evaluation (from https://github.com/gcc-mirror/gcc/blob/releases/gcc-12.2.0/libstdc++-v3/include/bits/basic_string.h#L356)

for (_CharT& __c : _M_local_buf)
    __c = _CharT();

in order to set the union member _M_local_buf to active and zero it.

The problem is that prior to this loop no member of the union has been set to active and setting a member (subobject) through a reference, rather than directly by name or through member access expressions, is technically not specified in the standard to set a member to active. As a consequence that technically has undefined behavior, which is why Clang (correctly) doesn't want to accept it as constant expression. If the member was set as active beforehand, e.g. with an extra line _M_local_buf[0] = _CharT(); it would be fine.

Now of course it seems a bit silly that setting _M_local_buf[0] directly is fine, but setting it through a reference __c isn't, but there may be good reasons for this. A reference cannot generally be guaranteed to refer to a specific object known at compile-time and so it might make it harder for a compiler to recognize whether or not the expression needs to restrict certain optimizations around it which wouldn't be valid if the active member is changed.


There is a libstdc++ bug report relating to a very similar issue here, but that is already supposed to be fixed for a while.

It seems that the commit https://github.com/gcc-mirror/gcc/commit/98a0d72a610a87e8e383d366e50253ddcc9a51dd has (re-)introduced the particular issue you are seeing here. Before the commit the member was set active correctly. Might be worth it to open an issue about this, although as mentioned above Clang is being very pedantic here and as the linked bug report also says, the standard should probably be changed to allow this.


UPDATE:

There is a commit for libstdc++ in GCC releases 13.1 and 12.3 that does fix the issue when compiling with Clang. See commit https://github.com/gcc-mirror/gcc/commit/52672be7d328df50f9a05ce3ab44ebcae50fee1b:

_M_local_buf[__i] = _CharT();

With an implicit (*this). added to _M_local_buf this now satisfies the form required in class.union.general/5 to start the lifetime of the _M_local_buf array member and therefore set it as active.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • Interesting. Didn't know about the lib issue. Thanks! It came up in a much longer piece of code that compiled and ran ok but created red squigglies on the top calling function with no info about the source of the issue. – doug Sep 12 '22 at 03:52
  • Thanks again for the additional detail. I'm more comfortable using my code. MSVC on windows mostly. Some on linux. – doug Sep 12 '22 at 05:07
  • 1
    *technically not specified in the standard to set a member to active* I think it was changed in C++20 via [P0137r1](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0137r1.html) – Fedor Jun 07 '23 at 13:43
  • In C++17 there was indeed a prohibition to change active union in constant expression member via arbitrary assignment expression, see [expr.const#2.10](https://timsong-cpp.github.io/cppwp/n4659/expr.const#2.10), but this point of the standard was removed in C++20. And GCC precisely implement it, changing its behavior depending on C++17 or C++20 mode: https://gcc.godbolt.org/z/G5KM7Pcs3 – Fedor Jun 08 '23 at 07:33
  • 1
    @Fedor That's true, but not the point made in my answer or the comment above. The rule that determines whether an assignment expression will change the active member (by starting the lifetime of a union member) is specified narrowly for specific syntax of the whole assignment expression in class.union.general/5. The problem is whether or not the expression in the code has the required syntactical form and therefore defined behavior, rather than undefined behavior for accessing an inactive member (regardless of whether in a constant expression or not). – user17732522 Jun 08 '23 at 08:21