**Background**: `__clear_and_shrink()` (added in https://reviews.llvm.org/D41976) is used in the copy assignment of allocators. It was added as an optimization to prevent calling shrink_to_fit(), which makes a new allocation when we have just called clear() on the string. Instead, it deallocates the allocated string and then sets the short string size to 0, making the active member of the union in __rep, __s I.e. a short string. It changes the representation to a short string to satisfy the invariant `data()[size()] != value_type()` and to prevent deallocation of the heap allocated long string a second time in the string destructor.
**Issue**: The problem with this function is that during constant evaluation, short string optimisation is not used so any read operations on __rep will try to access __l I.e. the long string. This will fail as it is illegal to change the active member of a union during constant evaluation.
**Reproducing**: The following code demonstrates the issue:
```
#include <cassert>
#include <string>
constexpr bool test()
{
std::string l = "Long string so that allocation definitely, for sure, absolutely happens. Probably.";
l.__clear_and_shrink();
return true;
}
int main(int, char**)
{
static_assert(test());
return 0;
}
```
This code fails with the error:
```
../src/clear_and_shrink_test.cpp:14:19: error: static assertion expression is not an integral constant expression
static_assert(test());
^~~~~~
/home/brendanemery/work_esr/llvm-project/build/include/c++/v1/string:1618:17: note: read of member '__l' of union with active member '__s' is not allowed in a constant expression
{return __r_.first().__l.__data_;}
^
/home/brendanemery/work_esr/llvm-project/build/include/c++/v1/string:2347:47: note: in call to '&l->__get_long_pointer()'
__alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap());
^
../src/clear_and_shrink_test.cpp:6:17: note: in call to '&l->~basic_string()'
std::string l = "Long string so that allocation definitely, for sure, absolutely happens. Probably.";
^
../src/clear_and_shrink_test.cpp:14:19: note: in call to 'test()'
static_assert(test());
```
I can also reproduce this issue in the copy assignment operator when using an allocator with `typedef std::true_type propagate_on_container_copy_assignment;`:
```
#include <cassert>
#include <string>
template <class T>
class other_allocator
{
int data_ = -1;
template <class U>
friend class other_allocator;
public:
typedef T value_type;
constexpr other_allocator() {}
constexpr explicit other_allocator(int i) : data_(i) {}
template <class U>
constexpr other_allocator(const other_allocator<U>& a) : data_(a.data_)
{
}
constexpr T* allocate(std::size_t n) { return std::allocator<value_type>().allocate(n); }
constexpr void deallocate(T* p, std::size_t s) { std::allocator<value_type>().deallocate(p, s); }
constexpr other_allocator select_on_container_copy_construction() const { return other_allocator(-2); }
constexpr friend bool operator==(const other_allocator& x, const other_allocator& y) { return x.data_ == y.data_; }
constexpr friend bool operator!=(const other_allocator& x, const other_allocator& y) { return !(x == y); }
typedef std::true_type propagate_on_container_copy_assignment;
typedef std::true_type propagate_on_container_move_assignment;
typedef std::true_type propagate_on_container_swap;
};
template <class S>
constexpr void testAlloc(S s, S str)
{
s = str;
}
constexpr bool test()
{
typedef other_allocator<char> A; // has POCCA --> true
typedef std::basic_string<char, std::char_traits<char>, A> S;
testAlloc(S(A(5)), S("1234567890123456789012345678901234567890123456789012345678901234567890", A(7)));
return true;
}
int main(int, char**)
{
test();
static_assert(test());
return 0;
}
```
This code fails with the error: