https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127109

            Bug ID: 127109
           Summary: [C++26] P0963 structured binding as a condition
                    rejected during constant evaluation when the type uses
                    the tuple protocol
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Keywords: c++26, rejects-valid
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: filip.sajdak at gmail dot com
  Target Milestone: ---

C++26 P0963R3 allows a structured binding declaration to be the condition of an
if. GCC implements this (PR115745). It works for aggregates, and it works at
run time for types that decompose through the tuple protocol. It is rejected
when all three of the following hold at once:

1. the structured binding declaration is used as a condition (P0963), and
2. the bound type decomposes via the tuple protocol (tuple_size / get<I>())
rather than member-wise, and
3. the call is evaluated in a constant expression.

Removing any one of the three makes GCC accept the program. Clang accepts every
variant.

Reproducer (only <cstddef> and <tuple>):

#include <cstddef>
#include <tuple>

struct Result {
    int a;
    int b;
    bool ok;

    constexpr explicit operator bool() const { return ok; }

    template <std::size_t I> constexpr int get() const { return I == 0 ? a : b;
}
};

template <> struct std::tuple_size<Result> :
std::integral_constant<std::size_t, 2> {};
template <std::size_t I> struct std::tuple_element<I, Result> { using type =
int; };

constexpr int sum_if_ok(int v) {
    if (auto [a, b] = Result{v, v * 2, v != 0}) {
        return a + b;
    }
    return -1;
}

static_assert(sum_if_ok(1) == 3);
static_assert(sum_if_ok(0) == -1);

int main() { return 0; }

Command:

  g++ -std=c++26 bug.cpp

No other flags and no third-party headers. -Wall -Wextra produces no additional
diagnostics.

Actual behaviour:

<source>:24:28: error: non-constant condition for static assertion
   24 | static_assert(sum_if_ok(1) == 3);
      |               ~~~~~~~~~~~~~^~~~
<source>:24:24:   in 'constexpr' expansion of 'sum_if_ok(1)'
<source>:18:5: error: accessing '<anonymous>' outside its lifetime
   18 |     if (auto [a, b] = Result{v, v * 2, v != 0}) {
      |     ^~
<source>:18:46: note: declared here

(The same pair of errors repeats for the second static_assert.)

Expected behaviour:

The program compiles and both assertions hold. The object introduced by the
structured binding declaration should remain within its lifetime for the
evaluation of the condition and the substatement, as it does at run time and as
it does for aggregates.

Versions tested (reproduces on the current release and on today's trunk
snapshot):

gcc version 16.2.0 (Compiler-Explorer-Build-gcc--binutils-2.44)
gcc version 17.0.0 20260826 (experimental)
(Compiler-Explorer-Build-gcc-6f7fd05bd3ba0ba41703589b29f82fa37fd0d9d5-binutils-2.44)
Target: x86_64-linux-gnu
Configured with: ../gcc-16.2.0/configure
--prefix=/opt/compiler-explorer/gcc-build/staging --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu --disable-bootstrap
--enable-multiarch --with-abi=m64 --with-multilib-list=m32,m64,mx32
--enable-multilib --enable-clocale=gnu
--enable-languages=c,c++,fortran,ada,objc,obj-c++,go,d,m2,rust,cobol,algol68
--enable-ld=yes --enable-gold=yes --enable-libstdcxx-time=yes
--enable-linker-build-id --enable-lto --enable-plugins --enable-threads=posix
--with-pkgversion=Compiler-Explorer-Build-gcc--binutils-2.44

These are Compiler Explorer builds, which is why the pkgversion string is
theirs; the links below reproduce it with no local setup.

Variants tested:

  #  decomposition                         form           evaluation  GCC
16.2/trunk  Clang 22.1
  1  aggregate (member-wise)               P0963 cond     constant    accepts  
      accepts
  2  tuple protocol                        P0963 cond     run time    accepts  
      accepts
  3  tuple protocol                        separate cond  constant    accepts  
      accepts
  4  tuple protocol, get() returns int     P0963 cond     constant    REJECTS  
      accepts
  5  tuple protocol, get() returns const&  P0963 cond     constant    REJECTS  
      accepts

Variant 3 is the workaround: declaring the binding first and writing the
condition separately is accepted everywhere.

Variant 5 is worth separating out. With get() returning int by value the
bindings are references bound to temporaries, so one plausible reading is that
this is about lifetime extension of those temporaries. It is not. Changing
get() to return const int& (with tuple_element<I, Result>::type correspondingly
const int) removes the temporaries entirely, and GCC still rejects it. The
object whose lifetime is reported as ended is the condition variable itself.

Compiler Explorer:

  GCC 16.2, rejected:   https://godbolt.org/z/MMxhKv7sT
  GCC trunk, rejected:  https://godbolt.org/z/qdqjcdbxh
  Clang 22.1, accepted: https://godbolt.org/z/vK1hsh14r

Possibly relevant: the P0963 implementation (r15-1793) introduced a TARGET_EXPR
to hold the result of the contextual conversion to bool across the get() calls
for tuple-protocol types, stashed in DECL_DECOMP_BASE. That is the code path
specific to the combination that fails here, and the aggregate case does not
exercise it.

How it was found: while checking whether P0963 shortens a compile-time regular
expression example. ctre::regex_results decomposes through the tuple protocol
and CTRE matches are routinely evaluated in constant expressions, so a
constexpr function using the P0963 spelling assembles all three conditions
without anyone aiming for the combination. Any tuple-protocol type with an
operator bool() reaches the same place.

Related: PR115745 (the implementation), PR118833 and PR116113 (fixed ICEs in
the same feature). PR117784 (P2686R4) is about declaring structured bindings
constexpr, which this testcase does not do; variant 3 above shows
tuple-protocol decomposition in a constant expression already works, so the
failure looks specific to the P0963 condition form rather than to P2686.
PR124584 also concerns the lifetime of a tuple-protocol structured binding, but
at run time under co_await.

Reply via email to