4

Below code compiles fine with clang but not gcc , any explanation is this a bug in gcc?

Its just a class which contains a vector of unique_ptr and std::function as member and when I create vector of this class , I cant say reserve or resize on this. push_back works fine with std::move, while this only happens with gcc and not clang.

#include <algorithm>                                                                                                                                               
#include <memory>                                                                                                                                                          
#include <utility>                                                                                                                                                         
#include <iostream>  
#include <vector>
#include <functional>

using namespace std;                                                                                                                                                       

class ABC                                                                                                                                                                  
{                                                                                                                                                                          
public:                                                                                                                                                                    
    ABC()                                                                                                                                                                  
          {}                                                                                                                                                               
  private:                                                                                                                                                                 
    std::vector<std::unique_ptr<int>> up;                                                                                                                                  
    std::function<void (int*)> func;                                                                                                                                       
};                                                                                                                                                                         

int main()                                                                                                                                                                 
{                                                                                                                                                                          
    ABC a;                                                                                                                                                                 
    std::vector<ABC> vec;                                                                                                                                                  
    vec.reserve(1);                                                                                                                                                        
}    

Error message looks like below for gcc

In file included from /opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_tempbuf.h:60:0,
                 from /opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_algo.h:62,
                 from /opt/wandbox/gcc-7.1.0/include/c++/7.1.0/algorithm:62,
                 from prog.cc:1:
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_construct.h: In instantiation of 'void std::_Construct(_T1*, _Args&& ...) [with _T1 = std::unique_ptr<int>; _Args = {const std::unique_ptr<int, std::default_delete<int> >&}]':
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_uninitialized.h:83:18:   required from 'static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _ForwardIterator = std::unique_ptr<int>*; bool _TrivialValueTypes = false]'
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_uninitialized.h:134:15:   required from '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _ForwardIterator = std::unique_ptr<int>*]'
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_uninitialized.h:289:37:   required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _ForwardIterator = std::unique_ptr<int>*; _Tp = std::unique_ptr<int>]'
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_vector.h:331:31:   required from 'std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = std::unique_ptr<int>; _Alloc = std::allocator<std::unique_ptr<int> >]'
prog.cc:10:7:   required from 'void std::_Construct(_T1*, _Args&& ...) [with _T1 = ABC; _Args = {const ABC&}]'
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_uninitialized.h:83:18:   required from 'static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const ABC*; _ForwardIterator = ABC*; bool _TrivialValueTypes = false]'
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_uninitialized.h:134:15:   required from '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const ABC*; _ForwardIterator = ABC*]'
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_uninitialized.h:289:37:   required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = const ABC*; _ForwardIterator = ABC*; _Tp = ABC]'
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_vector.h:1263:35:   required from 'std::vector<_Tp, _Alloc>::pointer std::vector<_Tp, _Alloc>::_M_allocate_and_copy(std::vector<_Tp, _Alloc>::size_type, _ForwardIterator, _ForwardIterator) [with _ForwardIterator = const ABC*; _Tp = ABC; _Alloc = std::allocator<ABC>; std::vector<_Tp, _Alloc>::pointer = ABC*; std::vector<_Tp, _Alloc>::size_type = long unsigned int]'
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/vector.tcc:73:40:   required from 'void std::vector<_Tp, _Alloc>::reserve(std::vector<_Tp, _Alloc>::size_type) [with _Tp = ABC; _Alloc = std::allocator<ABC>; std::vector<_Tp, _Alloc>::size_type = long unsigned int]'
prog.cc:24:18:   required from here
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/stl_construct.h:75:7: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
     { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /opt/wandbox/gcc-7.1.0/include/c++/7.1.0/memory:80:0,
                 from prog.cc:2:
/opt/wandbox/gcc-7.1.0/include/c++/7.1.0/bits/unique_ptr.h:388:7: note: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^~~~~~~~~~
Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
user8063157
  • 285
  • 1
  • 13

3 Answers3

3

I think that GCC is being picky about constructors; it blocks the generation of the move-ctor1. It stops complaining after you provide them explicitely:

class ABC                                                                                                                                                                  
{                                                                                                                                                                          
public:                                                                                                                                                                    
    ABC() = default;        // <-- 
    ABC(ABC&&) = default;   // <--
  private:                                                                                                                                                                 
    std::vector<std::unique_ptr<int>> up;                                                                                                                                  
    std::function<void (int*)> func;                                                                                                                                       
};

1 I think (again, I'm not sure), that his happens because the default copy-ctor of ABC is defined as noexcept and is as such preferred over the default move-ctor (it's still ill-formed because we're dealing with non-copyable members here). Trying to generate a defaulted noexcept move-ctor result in:

main.cpp:14:4: note: 'ABC::ABC(ABC&&) noexcept' is implicitly deleted because its exception-specification does not match the implicit exception-specification ''
    ABC(ABC&&) noexcept = default;
    ^~~

As such, forcing the generation of no-noexcept move-ctor allows it to be picked. Why Clang doesn't have a problem with that - I don't know.

One explanation for the above is hinted by the fact that removing std::function allows the defaulted noexcept move-ctor to be generated. std::function doesn't have a noexcept move-ctor (which sucks tremendously), so the whole class falls back to the copy-ctor. Since unique_ptr doesn't have one, the whole thing breaks.

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
3

This happens because std::function's move ctor is not noexcept, but a std::vector can only use the move ctor if it's noexcept (strong exception guarantee).

The issue is that std::unique_ptr is (obviously) non-copyable, so that makes ABC noncopyable as a whole. To make ABC noexcept-movable implicitly, it'd need every one of its members to be noexcept-movable as well.

If you remove the std::function, that's what happens: the .resize() doesn't need to copy A.up (a std::vector's move operator is noexcept), so the std::unique_ptrs (inside up) can just be moved, and everything works fine.

See this coliru: if you comment the noexcept, vec.reserve() will need to copy everything, and the issue comes back.

Adding ABC(ABC&&) = default fixes the issue because user-declared (though defaulted) move ctor inhibits the generation of copy constructor (see here).

We can also go the opposite way and manually delete A's copy constructor in the coliru (and keep the move ctor not noexcept): here.


To fix the issue, you could store the std::function in a std::unique_ptr, which has a nothrow move ctor. See here

Ven
  • 19,015
  • 2
  • 41
  • 61
  • It does help: [here with `noexcept` move ctor but deleted copy ctor](http://coliru.stacked-crooked.com/a/3282bd86ddd5515e) – Ven Jun 08 '17 at 09:26
  • My point was that its the manual declaration of `noexcept` move-ctor that's key. You don't have to touch the copy-ctor at all, and if you *only* try to delete it, it won't work. – Bartek Banachewicz Jun 08 '17 at 09:28
  • 1
    No, that's just because declaring a copy ctor prevents the declaration of a move ctor. This is why the coliru I linked to has the move ctor declared. – Ven Jun 08 '17 at 09:36
  • The root cause is that `vector>` pretends that it is copyable when it isn't. That, in turn, causes `ABC` to pretend to be copyable when it isn't. Then, `vector` goes "Are you nothrow movable? No? Are you copyable? Yes? Then I'll copy you so that we have strong exception safety." And bam, explosions. – T.C. Jun 08 '17 at 19:17
1

GCC accepted the lack of noexcept on std::function's move ctor as bug

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

// -- C++ --

  /** 2227        *  @brief %Function move constructor. 2228        *  @param
 __x A %function object rvalue with identical call signature. 2229        * 2230        
*  The newly-created %function contains the target of @a __x 2231        *  (if 
it has one). 2232        */ 2233       
function(function&& __x) : _Function_base()  {     __x.swap(*this); }
user8063157
  • 285
  • 1
  • 13