public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Antony Polukhin <antoshkka@gmail.com>
To: Jonathan Wakely <jwakely@redhat.com>
Cc: gcc-patches List <gcc-patches@gcc.gnu.org>,
	"libstdc++" <libstdc++@gcc.gnu.org>
Subject: Re: [PATCH] PR libstdc++/71579 assert that type traits are not misused with an incomplete type
Date: Thu, 12 Nov 2020 21:55:26 +0300	[thread overview]
Message-ID: <CAKqmYPanHYiOkMGMkuFq+FT2jMc78kbrU+kD-uAROfJdBFyVdA@mail.gmail.com> (raw)
In-Reply-To: <20200924175301.GK6061@redhat.com>

[-- Attachment #1: Type: text/plain, Size: 1464 bytes --]

Final bits for libstdc/71579

std::common_type assertions attempt to give a proper 'required from
here' hint for user code, do not bring many changes to the
implementation and check all the template parameters for completeness.
In some cases the type could be checked for completeness more than
once. This seems to be unsolvable due to the fact that
std::common_type could be specialized by the user, so we have to call
std::common_type recursively, potentially repeating the check for the
first type.

std::common_reference assertions make sure that we detect incomplete
types even if the user specialized the std::basic_common_reference.

Changelog:

2020-11-12  Antony Polukhin  <antoshkka@gmail.com>
    PR libstdc/71579
    * include/std/type_traits (is_convertible, is_nothrow_convertible)
    (common_type, common_reference): Add static_asserts
    to make sure that the arguments of the type traits are not misused
    with incomplete types.
    * testsuite/20_util/common_reference/incomplete_basic_common_neg.cc:
New test.
    * testsuite/20_util/common_reference/incomplete_neg.cc: New test.
    * testsuite/20_util/common_type/incomplete_neg.cc: New test.
    * testsuite/20_util/common_type/requirements/sfinae_friendly_1.cc: Remove
    SFINAE tests on incomplete types.
    * testsuite/20_util/is_convertible/incomplete_neg.cc: New test.
    * testsuite/20_util/is_nothrow_convertible/incomplete_neg.cc: New test.



--
Best regards,
Antony Polukhin

[-- Attachment #2: type_traits_hardening-4.txt --]
[-- Type: text/plain, Size: 19045 bytes --]

diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index 34e068b..00fa7f5 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -1406,12 +1406,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _From, typename _To>
     struct is_convertible
     : public __is_convertible_helper<_From, _To>::type
-    { };
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_From>{}),
+	"first template argument must be a complete class or an unbounded array");
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_To>{}),
+	"second template argument must be a complete class or an unbounded array");
+    };
 
   // helper trait for unique_ptr<T[]>, shared_ptr<T[]>, and span<T, N>
   template<typename _ToElementType, typename _FromElementType>
     using __is_array_convertible
-      = is_convertible<_FromElementType(*)[], _ToElementType(*)[]>;
+      = typename __is_convertible_helper<
+	_FromElementType(*)[], _ToElementType(*)[]>::type;
 
   template<typename _From, typename _To,
            bool = __or_<is_void<_From>, is_function<_To>,
@@ -1454,7 +1460,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _From, typename _To>
     struct is_nothrow_convertible
     : public __is_nt_convertible_helper<_From, _To>::type
-    { };
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_From>{}),
+	"first template argument must be a complete class or an unbounded array");
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_To>{}),
+	"second template argument must be a complete class or an unbounded array");
+    };
 
   /// is_nothrow_convertible_v
   template<typename _From, typename _To>
@@ -2239,7 +2250,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _Tp1, typename _Tp2>
     struct common_type<_Tp1, _Tp2>
     : public __common_type_impl<_Tp1, _Tp2>::type
-    { };
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp1>{}),
+	"each argument type must be a complete class or an unbounded array");
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp2>{}),
+	"each argument type must be a complete class or an unbounded array");
+    };
 
   template<typename...>
     struct __common_type_pack
@@ -2253,7 +2269,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     struct common_type<_Tp1, _Tp2, _Rp...>
     : public __common_type_fold<common_type<_Tp1, _Tp2>,
 				__common_type_pack<_Rp...>>
-    { };
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp1>{}),
+	"first argument type must be a complete class or an unbounded array");
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp2>{}),
+	"second argument type must be a complete class or an unbounded array");
+#ifdef __cpp_fold_expressions
+      static_assert((std::__is_complete_or_unbounded(
+	__type_identity<_Rp>{}) && ...),
+	"each argument type must be a complete class or an unbounded array");
+#endif
+    };
 
   // Let C denote the same type, if any, as common_type_t<T1, T2>.
   // If there is such a type C, type shall denote the same type, if any,
@@ -3315,9 +3341,10 @@ template <typename _From, typename _To>
 
   // If A and B are both rvalue reference types, ...
   template<typename _Xp, typename _Yp>
-    struct __common_ref_impl<_Xp&&, _Yp&&,
-      _Require<is_convertible<_Xp&&, __common_ref_C<_Xp, _Yp>>,
-	       is_convertible<_Yp&&, __common_ref_C<_Xp, _Yp>>>>
+    struct __common_ref_impl<_Xp&&, _Yp&&, _Require<
+      typename __is_convertible_helper<_Xp&&, __common_ref_C<_Xp, _Yp>>::type,
+      typename __is_convertible_helper<_Yp&&, __common_ref_C<_Xp, _Yp>>::type
+    >>
     { using type = __common_ref_C<_Xp, _Yp>; };
 
   // let D be COMMON-REF(const X&, Y&)
@@ -3326,8 +3353,9 @@ template <typename _From, typename _To>
 
   // If A is an rvalue reference and B is an lvalue reference, ...
   template<typename _Xp, typename _Yp>
-    struct __common_ref_impl<_Xp&&, _Yp&,
-      _Require<is_convertible<_Xp&&, __common_ref_D<_Xp, _Yp>>>>
+    struct __common_ref_impl<_Xp&&, _Yp&, _Require<
+      typename __is_convertible_helper<_Xp&&, __common_ref_D<_Xp, _Yp>>::type
+    >>
     { using type = __common_ref_D<_Xp, _Yp>; };
 
   // If A is an lvalue reference and B is an rvalue reference, ...
@@ -3374,7 +3402,12 @@ template <typename _From, typename _To>
   // If sizeof...(T) is one ...
   template<typename _Tp0>
     struct common_reference<_Tp0>
-    { using type = _Tp0; };
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp0>{}),
+	"each argument type must be a complete class or an unbounded array");
+
+      using type = _Tp0;
+    };
 
   template<typename _Tp1, typename _Tp2, int _Bullet = 1, typename = void>
     struct __common_reference_impl
@@ -3385,7 +3418,12 @@ template <typename _From, typename _To>
   template<typename _Tp1, typename _Tp2>
     struct common_reference<_Tp1, _Tp2>
     : __common_reference_impl<_Tp1, _Tp2>
-    { };
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp1>{}),
+	"each argument type must be a complete class or an unbounded array");
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp2>{}),
+	"each argument type must be a complete class or an unbounded array");
+    };
 
   // If T1 and T2 are reference types and COMMON-REF(T1, T2) is well-formed, ...
   template<typename _Tp1, typename _Tp2>
diff --git a/libstdc++-v3/testsuite/20_util/common_reference/incomplete_basic_common_neg.cc b/libstdc++-v3/testsuite/20_util/common_reference/incomplete_basic_common_neg.cc
new file mode 100644
index 0000000..9724ad0
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/common_reference/incomplete_basic_common_neg.cc
@@ -0,0 +1,38 @@
+// { dg-do compile { target c++20 } }
+
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-error "must be a complete class" "" { target *-*-* } 0 }
+
+#include <type_traits>
+
+class X;
+
+template <>
+struct std::basic_common_reference<X, X> {
+  using type = int;
+};
+
+void test01()
+{
+  std::common_reference<X>();		// { dg-error "required from here" }
+  std::common_reference<X, X>();		// { dg-error "required from here" }
+  std::common_reference<X, X, X>();	// { dg-error "required from here" }
+  std::common_reference<X, X, X, X>();	// { dg-error "required from here" }
+  std::common_reference<X, X, X, X, X>();	// { dg-error "required from here" }
+}
diff --git a/libstdc++-v3/testsuite/20_util/common_reference/incomplete_neg.cc b/libstdc++-v3/testsuite/20_util/common_reference/incomplete_neg.cc
new file mode 100644
index 0000000..c151d4a
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/common_reference/incomplete_neg.cc
@@ -0,0 +1,58 @@
+// { dg-do compile { target c++20 } }
+
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-error "must be a complete class" "" { target *-*-* } 0 }
+
+#include <type_traits>
+
+class X;
+class Y {};
+
+void test01()
+{
+  std::common_reference<X>();		// { dg-error "required from here" }
+  std::common_reference<X, int>();		// { dg-error "required from here" }
+  std::common_reference<X, int, int>();	// { dg-error "required from here" }
+  std::common_reference<X, int, int, int>();	// { dg-error "required from here" }
+  std::common_reference<X, int, int, int, int>();	// { dg-error "required from here" }
+
+  std::common_reference<int, X>();		// { dg-error "required from here" }
+  std::common_reference<int, X, int>();	// { dg-error "required from here" }
+  std::common_reference<int, X, int, int>();	// { dg-error "required from here" }
+  std::common_reference<int, X, int, int, int>();	// { dg-error "required from here" }
+
+  std::common_reference<int, int, X>();	// { dg-error "required from here" }
+  std::common_reference<int, int, X, int>();	// { dg-error "required from here" }
+  std::common_reference<int, int, X, int, int>();	// { dg-error "required from here" }
+
+  std::common_reference<int, int, int, X>();	// { dg-error "required from here" }
+  std::common_reference<int, int, int, X, int>();	// { dg-error "required from here" }
+
+  std::common_reference<int, int, int, int, X>();	// { dg-error "required from here" }
+
+  std::common_reference<X, int, int, int, X>();	// { dg-error "required from here" }
+  std::common_reference<int, X, int, int, X>();	// { dg-error "required from here" }
+  std::common_reference<int, int, X, int, X>();	// { dg-error "required from here" }
+  std::common_reference<int, int, int, X, X>();	// { dg-error "required from here" }
+
+  std::common_reference<Y, int, int, int, X>();	// { dg-error "required from here" }
+  std::common_reference<int, Y, int, int, X>();	// { dg-error "required from here" }
+  std::common_reference<int, int, Y, int, X>();	// { dg-error "required from here" }
+  std::common_reference<int, int, int, Y, X>();	// { dg-error "required from here" }
+}
diff --git a/libstdc++-v3/testsuite/20_util/common_type/incomplete_neg.cc b/libstdc++-v3/testsuite/20_util/common_type/incomplete_neg.cc
new file mode 100644
index 0000000..6c2641d
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/common_type/incomplete_neg.cc
@@ -0,0 +1,59 @@
+// { dg-do compile { target c++17 } }
+
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-error "must be a complete class" "" { target *-*-* } 0 }
+
+#include <type_traits>
+
+class X;
+class Y {};
+
+// Some of the following asserts require __cpp_fold_expressions to trigger
+void test01()
+{
+  std::common_type<X>();		// { dg-error "required from here" }
+  std::common_type<X, int>();		// { dg-error "required from here" }
+  std::common_type<X, int, int>();	// { dg-error "required from here" }
+  std::common_type<X, int, int, int>();	// { dg-error "required from here" }
+  std::common_type<X, int, int, int, int>();	// { dg-error "required from here" }
+
+  std::common_type<int, X>();		// { dg-error "required from here" }
+  std::common_type<int, X, int>();	// { dg-error "required from here" }
+  std::common_type<int, X, int, int>();	// { dg-error "required from here" }
+  std::common_type<int, X, int, int, int>();	// { dg-error "required from here" }
+
+  std::common_type<int, int, X>();	// { dg-error "required from here" }
+  std::common_type<int, int, X, int>();	// { dg-error "required from here" }
+  std::common_type<int, int, X, int, int>();	// { dg-error "required from here" }
+
+  std::common_type<int, int, int, X>();	// { dg-error "required from here" }
+  std::common_type<int, int, int, X, int>();	// { dg-error "required from here" }
+
+  std::common_type<int, int, int, int, X>();	// { dg-error "required from here" }
+
+  std::common_type<X, int, int, int, X>();	// { dg-error "required from here" }
+  std::common_type<int, X, int, int, X>();	// { dg-error "required from here" }
+  std::common_type<int, int, X, int, X>();	// { dg-error "required from here" }
+  std::common_type<int, int, int, X, X>();	// { dg-error "required from here" }
+
+  std::common_type<Y, int, int, int, X>();	// { dg-error "required from here" }
+  std::common_type<int, Y, int, int, X>();	// { dg-error "required from here" }
+  std::common_type<int, int, Y, int, X>();	// { dg-error "required from here" }
+  std::common_type<int, int, int, Y, X>();	// { dg-error "required from here" }
+}
diff --git a/libstdc++-v3/testsuite/20_util/common_type/requirements/sfinae_friendly_1.cc b/libstdc++-v3/testsuite/20_util/common_type/requirements/sfinae_friendly_1.cc
index 04eef50..a52f186 100644
--- a/libstdc++-v3/testsuite/20_util/common_type/requirements/sfinae_friendly_1.cc
+++ b/libstdc++-v3/testsuite/20_util/common_type/requirements/sfinae_friendly_1.cc
@@ -89,7 +89,7 @@ enum class ScEn;
 
 enum UnscEn : int;
 
-struct Ukn;
+
 
 union U
 {
@@ -240,7 +240,7 @@ static_assert(is_type<std::common_type<decltype(nullptr), int (B::*)() const>,
 static_assert(is_type<std::common_type<decltype(nullptr), const int B::*>,
 	      const int B::*>(), "");
 static_assert(is_type<std::common_type<Abstract&, Abstract&>, Abstract>(), "");
-static_assert(is_type<std::common_type<Ukn&, Ukn&>, Ukn>(), "");
+
 static_assert(is_type<std::common_type<ImplicitTo<B&>, B&>, B>(), "");
 static_assert(is_type<std::common_type<ImplicitTo<B&>&, B&&>, B>(), "");
 static_assert(is_type<std::common_type<UConv1, const Abstract*&>,
@@ -254,11 +254,6 @@ static_assert(is_type<std::common_type<const Abstract&&,
 				       const Abstract&&>, Abstract>(), "");
 static_assert(is_type<std::common_type<volatile Abstract&&,
 				       volatile Abstract&&>, Abstract>(), "");
-static_assert(is_type<std::common_type<Ukn&&, Ukn&&>, Ukn>(), "");
-static_assert(is_type<std::common_type<const Ukn&&, const Ukn&&>,
-	      Ukn>(), "");
-static_assert(is_type<std::common_type<volatile Ukn&&, volatile Ukn&&>,
-	      Ukn>(), "");
 
 static_assert(is_type<std::common_type<X1, X2>, RX12>(), "");
 static_assert(is_type<std::common_type<const X1, X2>, RX12>(), "");
@@ -280,13 +275,13 @@ static_assert(!has_type<std::common_type<U, U2>>(), "");
 static_assert(!has_type<std::common_type<PrivateImplicitTo<int>, int>>(), "");
 static_assert(!has_type<std::common_type<const PrivateImplicitTo<int>,
 	      int>>(), "");
-static_assert(!has_type<std::common_type<int, Ukn>>(), "");
+
 static_assert(!has_type<std::common_type<int, Abstract>>(), "");
-static_assert(!has_type<std::common_type<Ukn, Abstract>>(), "");
+
 static_assert(!has_type<std::common_type<int, void>>(), "");
 static_assert(!has_type<std::common_type<int, const volatile void>>(), "");
 static_assert(!has_type<std::common_type<Abstract, void>>(), "");
-static_assert(!has_type<std::common_type<Ukn, void>>(), "");
+
 static_assert(!has_type<std::common_type<int[4], void>>(), "");
 static_assert(!has_type<std::common_type<ScEn, void>>(), "");
 static_assert(!has_type<std::common_type<UnscEn, void>>(), "");
diff --git a/libstdc++-v3/testsuite/20_util/is_convertible/incomplete_neg.cc b/libstdc++-v3/testsuite/20_util/is_convertible/incomplete_neg.cc
new file mode 100644
index 0000000..7fe7f8b
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/is_convertible/incomplete_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do compile { target c++11 } }
+
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-error "must be a complete class" "" { target *-*-* } 0 }
+
+#include <type_traits>
+
+class X;
+
+void test01()
+{
+  std::is_convertible<X, int>();	// { dg-error "required from here" }
+  std::is_convertible<int, X>();	// { dg-error "required from here" }
+  std::is_convertible<X, X>();		// { dg-error "required from here" }
+}
diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/incomplete_neg.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/incomplete_neg.cc
new file mode 100644
index 0000000..9c1d2f4
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/incomplete_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do compile { target c++20 } }
+
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-error "must be a complete class" "" { target *-*-* } 0 }
+
+#include <type_traits>
+
+class X;
+
+void test01()
+{
+  std::is_nothrow_convertible<X, int>();	// { dg-error "required from here" }
+  std::is_nothrow_convertible<int, X>();	// { dg-error "required from here" }
+  std::is_nothrow_convertible<X, X>();		// { dg-error "required from here" }
+}

  reply	other threads:[~2020-11-12 18:55 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-05-06 11:20 Antony Polukhin
2019-05-08 21:10 ` Jonathan Wakely
2019-05-30  6:29   ` Antony Polukhin
2019-05-30 16:38     ` Jonathan Wakely
2019-05-31  7:27       ` Antony Polukhin
2019-05-31 10:37         ` Jonathan Wakely
2019-06-06 12:19           ` Jonathan Wakely
2019-06-20 17:49             ` Antony Polukhin
2019-06-20 17:57               ` Ville Voutilainen
2019-06-20 18:56                 ` Antony Polukhin
2020-08-12  8:19                   ` Antony Polukhin
2020-08-19 11:29                     ` Jonathan Wakely
2020-08-19 16:44                       ` Antony Polukhin
2020-08-20 15:31                       ` Antony Polukhin
2020-09-24  7:15                         ` Antony Polukhin
2020-09-24  9:29                           ` Jonathan Wakely
2020-09-24 17:53                         ` Jonathan Wakely
2020-11-12 18:55                           ` Antony Polukhin [this message]
2021-01-08 17:28                             ` Antony Polukhin
2021-05-07 18:00                               ` Antony Polukhin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAKqmYPanHYiOkMGMkuFq+FT2jMc78kbrU+kD-uAROfJdBFyVdA@mail.gmail.com \
    --to=antoshkka@gmail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jwakely@redhat.com \
    --cc=libstdc++@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).