Make std::function tolerate semantically non-CopyConstructible objects

Message ID 20180509132805.GA21234@redhat.com
State New
Headers show
Series
  • Make std::function tolerate semantically non-CopyConstructible objects
Related show

Commit Message

Jonathan Wakely May 9, 2018, 1:28 p.m.
To satisfy the CopyConstructible requirement a callable object stored in
a std::function must behave the same when copied from a const or
non-const source. If copying a non-const object doesn't produce an
equivalent copy then the behaviour is undefined. But we can make our
std::function more tolerant of such objects by ensuring we always copy
from a const lvalue.

Additionally use an if constexpr statement in the _M_get_pointer
function to avoid unnecessary instantiations in the discarded branch.

	* include/bits/std_function.h (_Base_manager::_M_get_pointer):
	Use constexpr if in C++17 mode.
	(_Base_manager::_M_clone(_Any_data&, const _Any_data&, true_type)):
	Copy from const object.
	* testsuite/20_util/function/cons/non_copyconstructible.cc: New.

Tested powerpc64le-linux, committed to trunk.
commit 98de94098559575d68a3a639b78c6c61475f1d9c
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed May 9 13:41:25 2018 +0100

    Make std::function tolerate semantically non-CopyConstructible objects
    
    To satisfy the CopyConstructible requirement a callable object stored in
    a std::function must behave the same when copied from a const or
    non-const source. If copying a non-const object doesn't produce an
    equivalent copy then the behaviour is undefined. But we can make our
    std::function more tolerant of such objects by ensuring we always copy
    from a const lvalue.
    
    Additionally use an if constexpr statement in the _M_get_pointer
    function to avoid unnecessary instantiations in the discarded branch.
    
            * include/bits/std_function.h (_Base_manager::_M_get_pointer):
            Use constexpr if in C++17 mode.
            (_Base_manager::_M_clone(_Any_data&, const _Any_data&, true_type)):
            Copy from const object.
            * testsuite/20_util/function/cons/non_copyconstructible.cc: New.

Patch

diff --git a/libstdc++-v3/include/bits/std_function.h b/libstdc++-v3/include/bits/std_function.h
index 96261355a9d..ee94d1ca81e 100644
--- a/libstdc++-v3/include/bits/std_function.h
+++ b/libstdc++-v3/include/bits/std_function.h
@@ -131,8 +131,8 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
   class _Function_base
   {
   public:
-    static const std::size_t _M_max_size = sizeof(_Nocopy_types);
-    static const std::size_t _M_max_align = __alignof__(_Nocopy_types);
+    static const size_t _M_max_size = sizeof(_Nocopy_types);
+    static const size_t _M_max_align = __alignof__(_Nocopy_types);
 
     template<typename _Functor>
       class _Base_manager
@@ -150,10 +150,13 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	static _Functor*
 	_M_get_pointer(const _Any_data& __source)
 	{
-	  const _Functor* __ptr =
-	    __stored_locally? std::__addressof(__source._M_access<_Functor>())
-	    /* have stored a pointer */ : __source._M_access<_Functor*>();
-	  return const_cast<_Functor*>(__ptr);
+	  if _GLIBCXX17_CONSTEXPR (__stored_locally)
+	    {
+	      const _Functor& __f = __source._M_access<_Functor>();
+	      return const_cast<_Functor*>(std::__addressof(__f));
+	    }
+	  else // have stored a pointer
+	    return __source._M_access<_Functor*>();
 	}
 
 	// Clone a location-invariant function object that fits within
@@ -170,7 +173,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_M_clone(_Any_data& __dest, const _Any_data& __source, false_type)
 	{
 	  __dest._M_access<_Functor*>() =
-	    new _Functor(*__source._M_access<_Functor*>());
+	    new _Functor(*__source._M_access<const _Functor*>());
 	}
 
 	// Destroying a location-invariant object may still require
diff --git a/libstdc++-v3/testsuite/20_util/function/cons/non_copyconstructible.cc b/libstdc++-v3/testsuite/20_util/function/cons/non_copyconstructible.cc
new file mode 100644
index 00000000000..d2a99925c58
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/function/cons/non_copyconstructible.cc
@@ -0,0 +1,39 @@ 
+// Copyright (C) 2018 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-do run { target c++11 } }
+
+#include <functional>
+
+// This type is not CopyConstructible because copying a non-const lvalue
+// will call the throwing constructor.
+struct A
+{
+  A() = default;
+  A(const A&) { } // not trivial, so allocated on the heap by std::function
+  A(A&) { throw 1; }
+  void operator()() const { }
+};
+
+int main()
+{
+  const A a{};
+  // Undefined, because std::function requires CopyConstructible:
+  std::function<void()> f(a);
+  // This will throw if the object is copied as non-const:
+  std::function<void()> g(f);
+}