From 587b3ca99916b0b58e662cf6e8d085c766f120a9 Mon Sep 17 00:00:00 2001
From: Odysseas Georgoudis <odygrd@hotmail.com>
Date: Mon, 27 Jul 2026 17:17:59 +0100
Subject: [PATCH] c++: Mark reads within pack expansion patterns [PR56958,
 PR124977]

make_pack_expansion calls mark_exp_read because a zero-length expansion
can discard its pattern.  That helper only follows expression forms whose
result directly denotes a declaration, so it misses reads nested inside
dependent operators and calls.

Walk expansion patterns while preserving read, discarded-value, and
assignment contexts.  This marks references whose values are consumed
without turning write-only assignment targets into reads.  Treat comma
fold patterns as discarded values, and retain the context of nested folds.

	PR c++/56958
	PR c++/57560
	PR c++/98706
	PR c++/124977

gcc/cp/ChangeLog:

	* cp-tree.h (make_pack_expansion): Add read-context parameter.
	* pt.cc (pack_expansion_usage): New enum.
	(mark_pack_expansion_read_data): New struct.
	(mark_pack_expansion_reads): New function.
	(mark_pack_expansion_read_r): New function.
	(make_pack_expansion): Walk expression patterns with the requested
	usage.
	* semantics.cc (finish_unary_fold_expr): Treat comma-fold patterns
	as discarded.
	(finish_binary_fold_expr): Likewise.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp26/pr124977.C: New test.
	* g++.dg/warn/pr56958-2.C: New test.
	* g++.dg/warn/pr56958.C: New test.

Signed-off-by: Odysseas Georgoudis <odygrd@hotmail.com>
---
 gcc/cp/cp-tree.h                      |   3 +-
 gcc/cp/pt.cc                          | 294 +++++++++++++++++++++-
 gcc/cp/semantics.cc                   |   8 +-
 gcc/testsuite/g++.dg/cpp26/pr124977.C |  36 +++
 gcc/testsuite/g++.dg/warn/pr56958-2.C | 336 ++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/warn/pr56958.C   |  90 +++++++
 6 files changed, 760 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp26/pr124977.C
 create mode 100644 gcc/testsuite/g++.dg/warn/pr56958-2.C
 create mode 100644 gcc/testsuite/g++.dg/warn/pr56958.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 87245184461..44e7ce3dda3 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8269,7 +8269,8 @@ extern tree uses_parameter_packs                (tree);
 extern bool template_parameter_pack_p           (const_tree);
 extern bool function_parameter_pack_p		(const_tree);
 extern bool function_parameter_expanded_from_pack_p (tree, tree);
-extern tree make_pack_expansion                 (tree, tsubst_flags_t = tf_warning_or_error);
+extern tree make_pack_expansion (tree, tsubst_flags_t = tf_warning_or_error,
+				 bool = true);
 extern tree make_pack_index			(tree, tree);
 extern bool check_for_bare_parameter_packs      (tree, location_t = UNKNOWN_LOCATION);
 extern tree build_template_info			(tree, tree);
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 6a081b838e0..fb82729ccf0 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -4325,13 +4325,294 @@ uses_parameter_packs (tree t)
   return parameter_packs;
 }
 
+/* How the value of an expression within a pack expansion is used.  */
+
+enum pack_expansion_usage
+{
+  peu_read,
+  peu_discarded,
+  peu_write
+};
+
+struct mark_pack_expansion_read_data
+{
+  tree root;
+  pack_expansion_usage usage;
+};
+
+static tree mark_pack_expansion_read_r (tree *, int *, void *);
+
+/* Walk *TP, marking reads according to USAGE.  Ordinary descendants are
+   read operands; nodes which impose another context are walked separately
+   below.  */
+
+static void
+mark_pack_expansion_reads (tree *tp, pack_expansion_usage usage)
+{
+  if (*tp)
+    {
+      mark_pack_expansion_read_data data = { *tp, usage };
+      cp_walk_tree_without_duplicates (tp, mark_pack_expansion_read_r, &data);
+    }
+}
+
+/* Callback for mark_pack_expansion_reads.  Unlike a simple tree walk, keep
+   assignment targets distinct from expressions whose values are read.  */
+
+static tree
+mark_pack_expansion_read_r (tree *tp, int *walk_subtrees, void *data)
+{
+  tree t = *tp;
+  mark_pack_expansion_read_data *read_data
+    = static_cast<mark_pack_expansion_read_data *> (data);
+  pack_expansion_usage usage
+    = (t == read_data->root ? read_data->usage : peu_read);
+
+  if (VAR_P (t) || TREE_CODE (t) == PARM_DECL)
+    {
+      if (usage != peu_write
+	  || (TREE_TYPE (t) && TYPE_REF_P (TREE_TYPE (t))))
+	mark_exp_read (t);
+      *walk_subtrees = 0;
+      return NULL_TREE;
+    }
+
+  if (FOLD_EXPR_P (t))
+    {
+      /* Preserve the context of a nested comma fold; every other fold
+	 operator consumes the values of its operands.  */
+      pack_expansion_usage fold_usage
+	= (FOLD_EXPR_OP (t) == COMPOUND_EXPR ? usage : peu_read);
+      tree pack = FOLD_EXPR_PACK (t);
+      if (PACK_EXPANSION_P (pack))
+	{
+	  mark_pack_expansion_reads (&PACK_EXPANSION_PATTERN (pack),
+				     fold_usage);
+	  mark_pack_expansion_reads (&PACK_EXPANSION_EXTRA_ARGS (pack),
+				     peu_read);
+	}
+      else
+	mark_pack_expansion_reads (&FOLD_EXPR_PACK (t), fold_usage);
+      if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR
+	  || TREE_CODE (t) == BINARY_RIGHT_FOLD_EXPR)
+	mark_pack_expansion_reads (&FOLD_EXPR_INIT (t), fold_usage);
+      *walk_subtrees = 0;
+      return NULL_TREE;
+    }
+
+  if (TREE_CODE (t) == STATEMENT_LIST)
+    {
+      for (tree_stmt_iterator i = tsi_start (t); !tsi_end_p (i); ++i)
+	mark_pack_expansion_reads (tsi_stmt_ptr (i), usage);
+      *walk_subtrees = 0;
+      return NULL_TREE;
+    }
+
+  if (TREE_CODE (t) == EXPR_STMT)
+    {
+      pack_expansion_usage expr_usage
+	= (EXPR_STMT_STMT_EXPR_RESULT (t) ? usage : peu_discarded);
+      mark_pack_expansion_reads (&EXPR_STMT_EXPR (t), expr_usage);
+      *walk_subtrees = 0;
+      return NULL_TREE;
+    }
+
+  if (TREE_CODE (t) == IF_STMT)
+    {
+      mark_pack_expansion_reads (&IF_COND (t), peu_read);
+      mark_pack_expansion_reads (&THEN_CLAUSE (t), peu_discarded);
+      mark_pack_expansion_reads (&ELSE_CLAUSE (t), peu_discarded);
+      *walk_subtrees = 0;
+      return NULL_TREE;
+    }
+
+  if (TREE_CODE (t) == FOR_STMT)
+    {
+      mark_pack_expansion_reads (&FOR_INIT_STMT (t), peu_discarded);
+      mark_pack_expansion_reads (&FOR_COND (t), peu_read);
+      mark_pack_expansion_reads (&FOR_EXPR (t), peu_discarded);
+      mark_pack_expansion_reads (&FOR_BODY (t), peu_discarded);
+      mark_pack_expansion_reads (&FOR_COND_PREP (t), peu_discarded);
+      mark_pack_expansion_reads (&FOR_COND_CLEANUP (t), peu_discarded);
+      *walk_subtrees = 0;
+      return NULL_TREE;
+    }
+
+  if (TREE_CODE (t) == BIND_EXPR)
+    {
+      for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl))
+	{
+	  mark_pack_expansion_reads (&DECL_INITIAL (decl), peu_read);
+	  mark_pack_expansion_reads (&DECL_SIZE (decl), peu_read);
+	  mark_pack_expansion_reads (&DECL_SIZE_UNIT (decl), peu_read);
+	}
+      mark_pack_expansion_reads (&BIND_EXPR_BODY (t), usage);
+      *walk_subtrees = 0;
+      return NULL_TREE;
+    }
+
+  if (usage == peu_read)
+    {
+      if (TREE_CODE (t) == COMPOUND_EXPR)
+	{
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_discarded);
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 1), peu_read);
+	  *walk_subtrees = 0;
+	}
+      return NULL_TREE;
+    }
+
+  switch (TREE_CODE (t))
+    {
+    case COMPOUND_EXPR:
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_discarded);
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 1), usage);
+      *walk_subtrees = 0;
+      break;
+
+    case COND_EXPR:
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_read);
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 1), usage);
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 2), usage);
+      *walk_subtrees = 0;
+      break;
+
+    case MODIFY_EXPR:
+    case INIT_EXPR:
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_write);
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 1), peu_read);
+      *walk_subtrees = 0;
+      break;
+
+    case MODOP_EXPR:
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_write);
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 2), peu_read);
+      *walk_subtrees = 0;
+      break;
+
+    case PREINCREMENT_EXPR:
+    case PREDECREMENT_EXPR:
+    case POSTINCREMENT_EXPR:
+    case POSTDECREMENT_EXPR:
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_write);
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 1), peu_read);
+      *walk_subtrees = 0;
+      break;
+
+    case PAREN_EXPR:
+    case CLEANUP_POINT_EXPR:
+    case SAVE_EXPR:
+    case WITH_CLEANUP_EXPR:
+    case STMT_EXPR:
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 0), usage);
+      *walk_subtrees = 0;
+      break;
+
+    case MUST_NOT_THROW_EXPR:
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 0), usage);
+      mark_pack_expansion_reads (&TREE_OPERAND (t, 1), peu_read);
+      *walk_subtrees = 0;
+      break;
+
+    case COMPONENT_REF:
+      if (usage == peu_write)
+	{
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_write);
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 2), peu_read);
+	  *walk_subtrees = 0;
+	}
+      break;
+
+    case ARRAY_REF:
+    case ARRAY_RANGE_REF:
+      if (usage == peu_write)
+	{
+	  tree base = TREE_OPERAND (t, 0);
+	  tree type = TREE_TYPE (base);
+	  pack_expansion_usage base_usage
+	    = (type && POINTER_TYPE_P (type) ? peu_read : peu_write);
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 0), base_usage);
+	  for (int i = 1; i < TREE_OPERAND_LENGTH (t); ++i)
+	    mark_pack_expansion_reads (&TREE_OPERAND (t, i), peu_read);
+	  *walk_subtrees = 0;
+	}
+      break;
+
+    case REALPART_EXPR:
+    case IMAGPART_EXPR:
+    case VIEW_CONVERT_EXPR:
+      if (usage == peu_write)
+	{
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_write);
+	  *walk_subtrees = 0;
+	}
+      break;
+
+    case BIT_FIELD_REF:
+      if (usage == peu_write)
+	{
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_write);
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 1), peu_read);
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 2), peu_read);
+	  *walk_subtrees = 0;
+	}
+      break;
+
+    case MEMBER_REF:
+    case DOTSTAR_EXPR:
+      if (usage == peu_write)
+	{
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_read);
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 1), peu_read);
+	  *walk_subtrees = 0;
+	}
+      break;
+
+    case SCOPE_REF:
+      if (usage == peu_write)
+	{
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_read);
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 1), peu_write);
+	  *walk_subtrees = 0;
+	}
+      break;
+
+    case EXPR_PACK_EXPANSION:
+      if (usage == peu_write)
+	{
+	  mark_pack_expansion_reads (&PACK_EXPANSION_PATTERN (t), peu_write);
+	  mark_pack_expansion_reads (&PACK_EXPANSION_EXTRA_ARGS (t), peu_read);
+	  *walk_subtrees = 0;
+	}
+      break;
+
+    case PACK_INDEX_EXPR:
+      if (usage == peu_write)
+	{
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 0), peu_write);
+	  mark_pack_expansion_reads (&TREE_OPERAND (t, 1), peu_read);
+	  *walk_subtrees = 0;
+	}
+      break;
+
+    default:
+      /* Other discarded expressions consume their operands.  Likewise,
+	 the operands used to compute an unrecognized lvalue are read.  Each
+	 descendant of this walk therefore defaults to peu_read.  */
+      break;
+    }
+
+  return NULL_TREE;
+}
+
 /* Turn ARG, which may be an expression, type, or a TREE_LIST
    representation a base-class initializer into a parameter pack
    expansion. If all goes well, the resulting node will be an
    EXPR_PACK_EXPANSION, TYPE_PACK_EXPANSION, or TREE_LIST,
-   respectively.  */
+   respectively.  READ_P controls whether the pattern is initially walked
+   in a read or discarded-value context.  */
 tree
-make_pack_expansion (tree arg, tsubst_flags_t complain)
+make_pack_expansion (tree arg, tsubst_flags_t complain, bool read_p)
 {
   tree result;
   tree parameter_packs = NULL_TREE;
@@ -4389,6 +4670,10 @@ make_pack_expansion (tree arg, tsubst_flags_t complain)
              initialization arguments.  */
           for (value = TREE_VALUE (arg); value; value = TREE_CHAIN (value))
             {
+	      /* Mark reads now, since an empty base pack discards the
+		 entire argument list.  */
+	      mark_pack_expansion_reads (&TREE_VALUE (value), peu_read);
+
               /* Determine which parameter packs will be expanded in this
                  argument.  */
               cp_walk_tree (&TREE_VALUE (value), &find_parameter_packs_r,
@@ -4424,8 +4709,9 @@ make_pack_expansion (tree arg, tsubst_flags_t complain)
       /* Propagate type and const-expression information.  */
       TREE_TYPE (result) = TREE_TYPE (arg);
       TREE_CONSTANT (result) = TREE_CONSTANT (arg);
-      /* Mark this read now, since the expansion might be length 0.  */
-      mark_exp_read (arg);
+      /* Mark reads in the pattern now, since the expansion might have
+	 length zero.  */
+      mark_pack_expansion_reads (&arg, read_p ? peu_read : peu_discarded);
     }
   else
     /* Just use structural equality for these TYPE_PACK_EXPANSIONS;
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 2274c6ab9b5..7378ae891ed 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -14847,7 +14847,9 @@ finish_unary_fold_expr (location_t loc, tree expr, int op, tree_code dir)
 		"unexpanded parameter packs");
       return error_mark_node;
     }
-  tree pack = make_pack_expansion (expr);
+  /* A comma fold must not turn write-only pattern operands into reads.  */
+  tree pack = make_pack_expansion (expr, tf_warning_or_error,
+				   abs (op) != COMPOUND_EXPR);
 
   /* Build the fold expression.  */
   tree code = build_int_cstu (integer_type_node, abs (op));
@@ -14879,7 +14881,9 @@ static tree
 finish_binary_fold_expr (location_t loc, tree pack, tree init,
 			 int op, tree_code dir)
 {
-  pack = make_pack_expansion (pack);
+  /* A comma fold must not turn write-only pattern operands into reads.  */
+  pack = make_pack_expansion (pack, tf_warning_or_error,
+			      abs (op) != COMPOUND_EXPR);
   tree code = build_int_cstu (integer_type_node, abs (op));
   tree fold = build_min_nt_loc (loc, dir, code, pack, init);
   FOLD_EXPR_MODIFY_P (fold) = (op < 0);
diff --git a/gcc/testsuite/g++.dg/cpp26/pr124977.C b/gcc/testsuite/g++.dg/cpp26/pr124977.C
new file mode 100644
index 00000000000..f0345cd517f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/pr124977.C
@@ -0,0 +1,36 @@
+// PR c++/124977
+// { dg-do compile { target c++26 } }
+// { dg-options "-Wall -Wextra" }
+
+struct nothing {};
+struct one { int value; };
+
+void use(int) {}
+
+// PR124977: FN and the structured binding pack are read by the call pattern.
+auto read(auto fn, auto value) // { dg-bogus "set but not used" }
+{
+  auto [...args] = value; // { dg-bogus "set but not used" }
+  (..., fn(args));
+}
+
+// Control case: merely declaring an empty binding pack is still unused.
+auto unused(auto value)
+{
+  auto [...args] = value; // { dg-warning "set but not used" }
+}
+
+// Negative case: assigning the bindings does not make them read.
+auto write(auto value)
+{
+  auto [...args] = value; // { dg-warning "set but not used" }
+  (..., use(((args = 0), 0)));
+}
+
+void test()
+{
+  read(0, nothing{});
+  read(use, one{});
+  unused(nothing{});
+  write(one{});
+}
diff --git a/gcc/testsuite/g++.dg/warn/pr56958-2.C b/gcc/testsuite/g++.dg/warn/pr56958-2.C
new file mode 100644
index 00000000000..c157f1afbb7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/pr56958-2.C
@@ -0,0 +1,336 @@
+// PR c++/56958
+// PR c++/98706
+// { dg-do compile { target c++17 } }
+// { dg-options "-Wall -Wextra" }
+
+// An empty arithmetic fold becomes 0, but N is used by its pattern.
+template<int... Is>
+int fold_parameter(int n) // { dg-bogus "set but not used" }
+{
+  return (((n) + Is) + ... + 0);
+}
+
+template int fold_parameter<>(int);
+
+// The local lambda is the callee in every expanded call.
+template<typename... T>
+constexpr auto fold_lambda(T... ts)
+{
+  auto id = [](auto x) { return x; }; // { dg-bogus "set but not used" }
+  return (id(ts) + ... + 0);
+}
+
+static_assert(fold_lambda() == 0);
+
+// Follow a read through a cast in the expansion pattern.
+template<int... Is>
+int fold_cast(int value) // { dg-bogus "set but not used" }
+{
+  return ((static_cast<int>(value) + Is) + ... + 0);
+}
+
+template int fold_cast<>(int);
+template int fold_cast<1>(int);
+
+// The condition and both possible results are read by ?:.
+template<int... Is>
+int conditional(bool c, int l, int r) // { dg-bogus "set but not used" }
+{
+  return (((c) ? (l) + Is : (r) + Is) + ... + 0);
+}
+
+template int conditional<>(bool, int, int);
+template int conditional<1>(bool, int, int);
+
+// TARGET is only assigned; the comma discards each assignment value.
+template<int... Is>
+int write_only()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  return (((target = Is), 0) + ... + 0);
+}
+
+template int write_only<>();
+
+// A comma fold also discards a direct assignment pattern.
+template<int... Is>
+void direct_write()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  (..., (target = Is));
+}
+
+template void direct_write<>();
+
+// The same distinction applies to a function parameter.
+template<int... Is>
+void parameter_write(int target) // { dg-warning "set but not used" }
+{
+  (..., (target = Is));
+}
+
+template void parameter_write<>(int);
+
+// Assigning through a reference reads the reference to locate its referent.
+template<int... Is>
+void reference_write(int& target) // { dg-bogus "set but not used" }
+{
+  (..., (target = Is));
+}
+
+template void reference_write<>(int&);
+
+// SOURCE is read to compute a value, while TARGET is only written.
+template<int... Is>
+int mixed_write(int source) // { dg-bogus "set but not used" }
+{
+  int target = 0; // { dg-warning "set but not used" }
+  return (((target = source + Is), 0) + ... + 0);
+}
+
+template int mixed_write<>(int);
+
+// Adding 0 consumes each assignment result, so TARGET counts as read.
+template<int... Is>
+int assignment_value()
+{
+  int target = 0; // { dg-bogus "set but not used" }
+  return (((target = Is) + 0) + ... + 0);
+}
+
+template int assignment_value<>();
+
+// Here the assignment results are direct operands of the arithmetic fold.
+template<int... Is>
+int direct_assignment_value()
+{
+  int target = 0; // { dg-bogus "set but not used" }
+  return ((target = Is) + ... + 0);
+}
+
+template int direct_assignment_value<>();
+template int direct_assignment_value<1>();
+
+template<typename... T>
+void consume(T...);
+
+// The outer comma discards the result of the already-built inner fold.
+template<int... Is>
+void nested_fold_write()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  consume(((((target = Is), ...), Is))...);
+}
+
+template void nested_fold_write<>();
+
+// Conversely, + consumes the inner fold result even if the outer pack is empty.
+template<int... Outer>
+struct nested_fold_reader
+{
+  template<int... Inner>
+  static int apply()
+  {
+    int target = 0; // { dg-bogus "set but not used" }
+    return (((((target = Inner), ...) + Outer) + ... + 0));
+  }
+};
+
+template int nested_fold_reader<>::apply<1>();
+
+// Passing an assignment result as an argument consumes that result.
+template<int... Is>
+void assignment_argument()
+{
+  int target = 0; // { dg-bogus "set but not used" }
+  consume((target = Is)...);
+}
+
+template void assignment_argument<>();
+template void assignment_argument<1>();
+
+// Explicitly converting the assignment itself to void suppresses the
+// warning.
+template<int... Is>
+void explicit_assignment_value()
+{
+  int target = 0; // { dg-bogus "set but not used" }
+  (..., static_cast<void>(target = Is));
+}
+
+template void explicit_assignment_value<>();
+
+// Converting the enclosing comma result does not consume its left assignment.
+template<int... Is>
+void explicit_discarded_assignment()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  (..., static_cast<void>(((target = Is), 0)));
+}
+
+template void explicit_discarded_assignment<>();
+
+// A non-result expression in a GNU statement expression is discarded.
+template<int... Is>
+int statement_write()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  return ((({
+    target = 1;
+    0;
+  }) + Is) + ... + 0);
+}
+
+template int statement_write<>();
+
+// The final expression reads VALUE, whose initializer in turn reads SOURCE.
+template<int... Is>
+int statement_read(int source) // { dg-bogus "set but not used" }
+{
+  return ((({
+    int value = source;
+    value;
+  }) + Is) + ... + 0);
+}
+
+template int statement_read<>(int);
+
+// Even the final statement-expression value is discarded by this comma fold.
+template<int... Is>
+void direct_statement_write()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  (..., ({
+    0;
+    target = Is;
+  }));
+}
+
+template void direct_statement_write<>();
+
+// A for-loop increment expression is a discarded-value expression.
+template<int... Is>
+int loop_write()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  return ((({
+    for (int i = 0; i != 1; target = 1)
+      ++i;
+    0;
+  }) + Is) + ... + 0);
+}
+
+template int loop_write<>();
+
+// ?: reads its condition, but both comma expressions only write TARGET.
+template<int... Is>
+int conditional_write(bool condition, int source) // { dg-bogus "set but not used" }
+{
+  int target = 0; // { dg-warning "set but not used" }
+  return ((condition
+	   ? ((target = source + Is), 0)
+	   : ((target = source - Is), 0))
+	  + ... + 0);
+}
+
+template int conditional_write<>(bool, int);
+
+// Statement conditions and right-hand sides are read; branch assignments are
+// only writes.
+template<int... Is>
+int if_statement_write(bool condition, int source) // { dg-bogus "set but not used" }
+{
+  int target = 0; // { dg-warning "set but not used" }
+  return ((({
+    if (condition)
+      target = source;
+    else
+      target = -source;
+    0;
+  }) + Is) + ... + 0);
+}
+
+template int if_statement_write<>(bool, int);
+
+struct Holder
+{
+  int member;
+};
+
+// Assigning a member only sets the containing local object.
+template<int... Is>
+int member_write()
+{
+  Holder object{}; // { dg-warning "set but not used" }
+  return (((object.member = Is), 0) + ... + 0);
+}
+
+template int member_write<>();
+
+// A pointer and index must be read to locate the object being assigned.
+template<int... Is>
+int indirect_write(int *pointer, int index) // { dg-bogus "set but not used" }
+{
+  return (((pointer[index + Is] = 0), 0) + ... + 0);
+}
+
+template int indirect_write<>(int *, int);
+
+// Dereferencing a pointer reads the pointer even though the referent is written.
+template<int... Is>
+int dereference_write(int *pointer) // { dg-bogus "set but not used" }
+{
+  return ((((*pointer = Is), 0)) + ... + 0);
+}
+
+template int dereference_write<>(int *);
+
+// A local array is only written, although INDEX is read.
+template<int... Is>
+int array_write(int index) // { dg-bogus "set but not used" }
+{
+  int target[1]{}; // { dg-warning "set but not used" }
+  return (((target[index + Is] = 0), 0) + ... + 0);
+}
+
+template int array_write<>(int);
+
+// Preserve the existing treatment of both .* operands as reads.
+template<int... Is>
+int member_pointer_write(int Holder::*member) // { dg-bogus "set but not used" }
+{
+  Holder object{}; // { dg-bogus "set but not used" }
+  return ((((object.*member) = Is), 0) + ... + 0);
+}
+
+template int member_pointer_write<>(int Holder::*);
+
+// At the strict warning level, a discarded compound update is only a write.
+template<int... Is>
+int update_only()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  return (((target += Is), 0) + ... + 0);
+}
+
+template int update_only<>();
+
+// Using the compound assignment result makes TARGET read.
+template<int... Is>
+int update_value()
+{
+  int target = 0; // { dg-bogus "set but not used" }
+  return (((target += Is) + 0) + ... + 0);
+}
+
+template int update_value<>();
+
+// A discarded increment is likewise only a write at the strict level.
+template<int... Is>
+int increment_only()
+{
+  int target = 0; // { dg-warning "set but not used" }
+  return ((((++target, Is), 0)) + ... + 0);
+}
+
+template int increment_only<>();
diff --git a/gcc/testsuite/g++.dg/warn/pr56958.C b/gcc/testsuite/g++.dg/warn/pr56958.C
new file mode 100644
index 00000000000..6393342b524
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/pr56958.C
@@ -0,0 +1,90 @@
+// PR c++/56958
+// PR c++/57560
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wall -Wextra" }
+
+// Original PR: SPURIOUS is read by a call argument expansion.
+template<typename... T>
+int original(T... t)
+{
+  int spurious = 0; // { dg-bogus "set but not used" }
+  return false ? original(t + spurious...) : 0;
+}
+
+template int original<>();
+template int original<int>(int);
+
+struct Object
+{
+  int method(int) const { return 0; }
+};
+
+template<typename... T>
+void expand(T&&...);
+
+template<int...>
+struct index_sequence {};
+
+template<typename...>
+struct TemplateStruct
+{
+  static constexpr Object get_object() { return {}; }
+
+  // Louis Dionne's example: OBJECT is read as a nested call operand.
+  template<int... i>
+  static void apply(index_sequence<i...>)
+  {
+    constexpr auto object = get_object(); // { dg-bogus "set but not used" }
+    expand(object.method(i)...);
+  }
+};
+
+template void TemplateStruct<>::apply(index_sequence<>);
+template void TemplateStruct<>::apply(index_sequence<0>);
+
+template<typename... T>
+struct base
+{
+  template<typename... U>
+  base(U...);
+};
+
+template<typename V, typename... T>
+struct derived : base<T...>
+{
+  // PR57560: VALUE is read by each expanded base-initializer argument.
+  derived(V value) // { dg-bogus "set but not used" }
+    : base<T...>(static_cast<T>(value)...)
+  {}
+};
+
+derived<int> empty(1);
+derived<int, long> nonempty(1);
+
+struct direct_base
+{
+  direct_base(int);
+};
+
+template<typename... Bases>
+struct direct_derived : Bases...
+{
+  // An empty base pack discards its argument list, but VALUE is still read.
+  direct_derived(int value) // { dg-bogus "set but not used" }
+    : Bases(value)...
+  {}
+};
+
+direct_derived<> direct_empty(1);
+direct_derived<direct_base> direct_nonempty(1);
+
+template<typename... Bases>
+struct direct_write : Bases...
+{
+  // The comma discards the assignment even when the base pack is empty.
+  direct_write(int target) // { dg-warning "set but not used" }
+    : Bases((target = 1, 0))...
+  {}
+};
+
+direct_write<> direct_write_empty(1);
-- 
2.43.5

