Bug 112642 - ranges::fold_left tries to access inactive union member of string in constant expression
Summary: ranges::fold_left tries to access inactive union member of string in constant...
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 13.2.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: rejects-valid
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2023-11-20 15:28 UTC by Miro Palmu
Modified: 2025-01-03 18:00 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2023-11-20 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Miro Palmu 2023-11-20 15:28:50 UTC
Following example will fail to compile. Error message is included below but most important part is here:

error: accessing 'std::__cxx11::basic_string<char>::<unnamed union>::_M_allocated_capacity' member instead of initialized 'std::__cxx11::basic_string<char>::<unnamed union>::_M_local_buf' member in constant expression

Example:

#include <string>
#include <functional>
#include <algorithm>
#include <vector>

using namespace std;
using namespace std::literals;

constexpr auto foo() {
    const auto vec = vector{ "a"s, "b"s, "c"s };
    const auto concat = ranges::fold_left(vec, ""s, plus{});
    return concat.size();
}

int main() {
    constexpr auto _ = foo();
}

Error message:

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --enable-languages=ada,c,c++,d,fortran,go,lto,objc,obj-c++ --enable-bootstrap --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --with-build-config=bootstrap-lto --with-linker-hash-style=gnu --with-system-zlib --enable-__cxa_atexit --enable-cet=auto --enable-checking=release --enable-clocale=gnu --enable-default-pie --enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object --enable-libstdcxx-backtrace --enable-link-serialization=1 --enable-linker-build-id --enable-lto --enable-multilib --enable-plugin --enable-shared --enable-threads=posix --disable-libssp --disable-libstdcxx-pch --disable-werror
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.2.1 20230801 (GCC) 
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++23' '-shared-libgcc' '-mtune=generic' '-march=x86-64' '-dumpdir' 'a-'
 /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/cc1plus -E -quiet -v -D_GNU_SOURCE prog.cpp -mtune=generic -march=x86-64 -std=c++23 -fpch-preprocess -o a-prog.ii
ignoring nonexistent directory "/usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../x86_64-pc-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1
 /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/x86_64-pc-linux-gnu
 /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/backward
 /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/include
 /usr/local/include
 /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/include-fixed
 /usr/include
End of search list.
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++23' '-shared-libgcc' '-mtune=generic' '-march=x86-64' '-dumpdir' 'a-'
 /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/cc1plus -fpreprocessed a-prog.ii -quiet -dumpdir a- -dumpbase prog.cpp -dumpbase-ext .cpp -mtune=generic -march=x86-64 -std=c++23 -version -o a-prog.s
GNU C++23 (GCC) version 13.2.1 20230801 (x86_64-pc-linux-gnu)
	compiled by GNU C version 13.2.1 20230801, GMP version 6.3.0, MPFR version 4.2.0-p12, MPC version 1.3.1, isl version isl-0.26-GMP

warning: MPFR header version 4.2.0-p12 differs from library version 4.2.1.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 5a490a353c29b926850bca65a518c219
prog.cpp: In function ‘int main()’:
prog.cpp:16:27:   in ‘constexpr’ expansion of ‘foo()’
prog.cpp:16:28:   in ‘constexpr’ expansion of ‘std::ranges::__fold_left_fn::operator()(_Range&&, _Tp, _Fp) const [with _Range = const std::vector<std::__cxx11::basic_string<char>, std::allocator<std::__cxx11::basic_string<char> > >&; _Tp = std::__cxx11::basic_string<char>; _Fp = std::plus<void>](vec, std::literals::string_literals::operator""s(const char*, std::size_t)(0), (std::plus<void>(), std::plus<void>()))’
prog.cpp:16:28:   in ‘constexpr’ expansion of ‘std::__cxx11::basic_string<char>((* & std::move<__cxx11::basic_string<char>&>(__init)))’
prog.cpp:16:28: error: accessing ‘std::__cxx11::basic_string<char>::<unnamed union>::_M_allocated_capacity’ member instead of initialized ‘std::__cxx11::basic_string<char>::<unnamed union>::_M_local_buf’ member in constant expression
   16 |     constexpr auto _ = foo();
      |                            ^
Comment 1 Jonathan Wakely 2023-11-20 15:35:07 UTC
I think the libstdc++ code is fine here, suggesting a compiler bug.

Slightly reduced:

#include <string>
#include <vector>

using namespace std;
using namespace std::literals;

struct Fold
{
  template<typename _Iter, typename _Sent, typename _Tp, typename _Fp>
      constexpr auto
      operator()(_Iter, _Sent, _Tp __init, _Fp) const
      {
	return std::move(__init);
      }

    template<typename _Range, typename _Tp, typename _Fp>
      constexpr auto
      operator()(_Range&& __r, _Tp __init, _Fp __f) const
      { return (*this)(__r.begin(), __r.end(), std::move(__init), std::move(__f)); }

} fold;

constexpr auto foo() {
    const auto vec = vector{ "a"s, "b"s, "c"s };
    return fold(vec, ""s, plus{});
}

constexpr auto bar() {
    return foo().size();
}

int main() {
    constexpr auto _ = bar();
}
Comment 2 Jonathan Wakely 2023-11-20 16:02:55 UTC
Further reduced:

#include <string>

using namespace std::literals;

template<typename T>
constexpr auto
fold2(T init)
{ return std::move(init); }

template<typename T>
constexpr auto
fold(T init)
{ return fold2(std::move(init)); }

constexpr auto foo() {
    return fold(""s);
}

constexpr auto bar() {
    return foo().size();
}

int main() {
    constexpr auto i = bar();
    return i;
}
Comment 3 Miro Palmu 2023-11-20 16:50:59 UTC
Further reduced:

#include <string>
using namespace std::literals;

consteval void bar() {
    auto _ = [](auto s) { return s; }(""s);
}

int main() {
    bar();    
    return 0;
}
Comment 4 Jonathan Wakely 2023-11-20 17:10:04 UTC
Or:

#include <string>

consteval void bar() {
    auto _ = [](std::string s) { return s; }({});
}

int main() {
    bar();    
}

Or:

#include <string>
constexpr auto foo(std::string init) { return init; }
constexpr auto bar() { return foo("").size(); }
constexpr auto i = bar();

Clang compiles both of these without problems (it can't compile anything using ""s in a constant expression, maybe due to https://github.com/llvm/llvm-project/issues/68527)

So I am pretty sure this is a g++ front end bug.
Comment 5 Miro Palmu 2023-11-21 17:48:12 UTC
I have been trying to figure out where exactly the bug is and these are my findings.
> Or:
>
> #include <string>
>
> consteval void bar() {
>     auto _ = [](std::string s) { return s; }({});
> }
>
> int main() {
>    bar();    
> }
>
> Or:
> 
> #include <string>
> constexpr auto foo(std::string init) { return init; }
> constexpr auto bar() { return foo("").size(); }
> constexpr auto i = bar();
>
>Clang compiles both of these without problems (it can't compile anything using ""s in a constant expression, maybe due to https://github.com/llvm/llvm-project/issues/68527)
>
> So I am pretty sure this is a g++ front end bug.

If you use libstdc++ on clang these will not compile but with different errors.

Then with following example I try to showcase the bug without std::string.
Try it out: https://godbolt.org/z/rvoeMEaxc

This is bare minimum of  libstdc++ basic_string to reproduce this bug:

---

struct S {
    union {
        char a[1];
    };
    char* ptr; 
    constexpr S() : ptr{a} {
        a[0] = {};
    }
    constexpr S(S&&) = delete;
    constexpr S(const S&) = delete;
    constexpr S operator=(S&&) = delete;
    constexpr S operator=(const S&) = delete;
    constexpr ~S() = default;
}

---

Then to reproduce the bug instance of this class has to be function parameter
and the function has to be constant evaluated.
This can happens in std::basic_string move constructor bits/basic_string.h:682 and following tester functions tries to emulate what happens in it.

---

// Should always be false
constexpr bool test(const S& s){
    return s.ptr != s.a;
}
consteval void tester1(S param = {}) { 
    S notparam = {};
    if (test(notparam)){
        throw std::logic_error("compiletime notparam!");
    }

    if (test(param)) {
        // gcc ends up here so fails to compile
        // in std::basic_string move constructor
        // compilation would fail due to accessing
        // inactive union member
        // clang and msvc never end up here

        throw std::logic_error("compiletime param");
    }
}

int main() { tester(); )

---

Notice that here only the parameter version fails.
In non-constant evaluated context (see godbolt link)
all of the test evaluate false as they should.
Comment 6 Jonathan Wakely 2023-11-21 20:14:44 UTC
(In reply to Miro Palmu from comment #5)
> If you use libstdc++ on clang these will not compile but with different
> errors.

The examples in comment 4 do compile using libstdc++ on clang, if you use libstdc++ headers from after sept 29 (for trunk) or oct 21 (for gcc-13).
Comment 7 Miro Palmu 2023-11-22 11:22:16 UTC
(In reply to Jonathan Wakely from comment #6)
> The examples in comment 4 do compile using libstdc++ on clang, if you use
> libstdc++ headers from after sept 29 (for trunk) or oct 21 (for gcc-13).

I was testing this on compiler explorer on clang 17.0.1 and it used gcc-13.2.0 libstdc++. Also tried it locally with clang 16.0.6 with gcc-13.2.1 libstdc++

Output:

$ cat prog.cpp 

#include <string>
#include <utility>
int main() {
    [](std::string s = {}) consteval {
        std::string ss{ std::move(s) };
    }();
}

$ clang prog.cpp -std=c++2b -stdlib=libstdc++

prog.cpp:4:5: error: call to consteval function 'main()::(anonymous class)::operator()' is not a constant expression
    [](std::string s = {}) consteval {
    ^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/stl_construct.h:97:14: note: construction of subobject of member '_M_local_buf' of union with no active member is not allowed in a constant expression
    { return ::new((void*)__location) _Tp(std::forward<_Args>(__args)...); }
             ^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/char_traits.h:272:6: note: in call to 'construct_at(&ss.._M_local_buf[0], s.._M_local_buf[0])'
            std::construct_at(__s1 + __i, __s2[__i]);
            ^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/char_traits.h:443:11: note: in call to 'copy(&ss.._M_local_buf[0], &s.._M_local_buf[0], 1)'
          return __gnu_cxx::char_traits<char_type>::copy(__s1, __s2, __n);
                 ^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/basic_string.h:672:6: note: in call to 'copy(&ss.._M_local_buf[0], &s.._M_local_buf[0], 1)'
            traits_type::copy(_M_local_buf, __str._M_local_buf,
            ^
prog.cpp:5:21: note: in call to 'basic_string(s)'
        std::string ss{ std::move(s) };
                    ^
prog.cpp:4:5: note: in call to '&[](std::string s) {
    std::string ss{std::move(s)};
}->operator()({{{{}}, &s.._M_local_buf[0]}, 0, {._M_local_buf = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...}}})'
    [](std::string s = {}) consteval {
    ^
1 error generated.
Comment 8 Jonathan Wakely 2023-11-22 12:04:56 UTC
(In reply to Miro Palmu from comment #7)
> (In reply to Jonathan Wakely from comment #6)
> > The examples in comment 4 do compile using libstdc++ on clang, if you use
> > libstdc++ headers from after sept 29 (for trunk) or oct 21 (for gcc-13).
> 
> I was testing this on compiler explorer on clang 17.0.1 and it used
> gcc-13.2.0 libstdc++.

Which is expected to fail, because 13.2.0 was released before Oct 21.

> Also tried it locally with clang 16.0.6 with
> gcc-13.2.1 libstdc++

Which gcc-13.2.1 though? That's a snapshot that could date from any time in the past four months. If I use gcc version 13.2.1 20231025 then clang compiles it.

Anyway, the original GCC error is the same as PR 112642 which was apparently reduced to PR 111284, which does seem relevant.
Comment 9 Miro Palmu 2023-11-22 12:31:53 UTC
(In reply to Jonathan Wakely from comment #8)
> > Also tried it locally with clang 16.0.6 with
> > gcc-13.2.1 libstdc++
> 
> Which gcc-13.2.1 though? That's a snapshot that could date from any time in
> the past four months. If I use gcc version 13.2.1 20231025 then clang
> compiles it.

Mine is 13.2.1 20230801 so way before Oct 21. (I did not know there were different snapshots of the releases, I'm just a user trying to help :) )

> Anyway, the original GCC error is the same as PR 112642

You probably mean PR 110158
Comment 10 Jonathan Wakely 2023-11-22 17:19:33 UTC
(In reply to Miro Palmu from comment #9)
> Mine is 13.2.1 20230801 so way before Oct 21. (I did not know there were
> different snapshots of the releases, I'm just a user trying to help :) )

13.2.1 (and any x.y.1 version) is not a release, it's a snapshot made from a branch between releases. See https://gcc.gnu.org/develop.html#num_scheme or more details.

Releases end with a .0 number.

> > Anyway, the original GCC error is the same as PR 112642
> 
> You probably mean PR 110158

Oops! I meant PR 111258