Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::optional vs std::optional for non copyable objects

I tried to update from VS2015 to VS2017 and the code below does not work in the newer version. As you can see a move constructor is defined which automatically deletes the copy constructor.

#include <boost/optional.hpp>
#include <vector>

struct Foo {
    Foo() {}
    Foo(Foo&& other) {}
};

int main() {
    std::vector<boost::optional<Foo>> foos;
    foos.resize(42);
    return 0;
}

The compilation error is

1>...\boost\dist\include\boost-1_66\boost\optional\optional.hpp(384): error C2280: 'Foo::Foo(const Foo &)': attempting to reference a deleted function
1>...\main.cpp(7): note: compiler has generated 'Foo::Foo' here
1>...\boost\dist\include\boost-1_66\boost\optional\optional.hpp(383): note: while compiling class template member function 'void boost::optional_detail::optional_base<T>::construct(const Foo &)'
1>        with
1>        [
1>            T=Foo
1>        ]
1>...\boost\dist\include\boost-1_66\boost\optional\optional.hpp(181): note: see reference to function template instantiation 'void boost::optional_detail::optional_base<T>::construct(const Foo &)' being compiled
1>        with
1>        [
1>            T=Foo
1>        ]
1>...\boost\dist\include\boost-1_66\boost\optional\optional.hpp(831): note: see reference to class template instantiation 'boost::optional_detail::optional_base<T>' being compiled
1>        with
1>        [
1>            T=Foo
1>        ]
1>...\msvc\14.12.25827\include\vector(1902): note: see reference to class template instantiation 'boost::optional<Foo>' being compiled
1>...\msvc\14.12.25827\include\vector(1901): note: while compiling class template member function 'boost::optional<Foo> *std::vector<boost::optional<Foo>,std::allocator<_Ty>>::_Udefault(boost::optional<Foo> *,const unsigned __int64)'
1>        with
1>        [
1>            _Ty=boost::optional<Foo>
1>        ]
1>...\msvc\14.12.25827\include\vector(1528): note: see reference to function template instantiation 'boost::optional<Foo> *std::vector<boost::optional<Foo>,std::allocator<_Ty>>::_Udefault(boost::optional<Foo> *,const unsigned __int64)' being compiled
1>        with
1>        [
1>            _Ty=boost::optional<Foo>
1>        ]
1>...\main.cpp(10): note: see reference to class template instantiation 'std::vector<boost::optional<Foo>,std::allocator<_Ty>>' being compiled
1>        with
1>        [
1>            _Ty=boost::optional<Foo>
1>        ]
1>...\main.cpp(7): note: 'Foo::Foo(const Foo &)': function was implicitly deleted because 'Foo' has a user-defined move constructor

Now the interesting thing is that it does compile when I use std::optional instead of boost::optional. I am not really sure what the issue is and who to blame: me, boost, microsoft, the c++ standard? Anyone knows what is going on?

Is this a known issue? Is it a bug in boost or is it correct that it does not work?

like image 899
Daniel Avatar asked May 13 '26 07:05

Daniel


1 Answers

This appears to be an issue with the STL implementations.

From the latest draft, n4700:

26.2.1 General container requirements [container.requirements.general] defines DefaultInsertable and MoveInsertable, and also states in part:

T is CopyInsertable into X means that, in addition to T being MoveInsertable into X, the following expression is well-formed:

allocator_traits<A>::construct(m, p, v)

and its evaluation causes the following postcondition to hold: The value of v is unchanged and is equivalent to *p.

(In this case, T is boost::optional<Foo>, X is std::vector<T>.)

Clearly, T is DefaultInsertable and MoveInsertable but not CopyInsertable.

26.3.11.3 vector capacity [vector.capacity] states in part:

void resize(size_type sz);

Effects: If sz < size(), erases the last size() - sz elements from the sequence. Otherwise, appends sz - size() default-inserted elements to the sequence.

Requires: T shall be MoveInsertable and DefaultInsertable into *this.

Remarks: If an exception is thrown other than by the move constructor of a non-CopyInsertable T there are no effects.

Although I do not possess official copies of C++11 or C++14, based on working copies, C++11 did not have the "Remarks:" paragraph and had a slightly different "Requires:" paragraph:

Requires: T shall be CopyInsertable into *this.

Therefore, foos.resize(42) is not well-formed in C++11, but it should be well-formed in C++14 because the requirements that T be MoveInsertable and DefaultInsertable into the vector are satisfied.

I have confirmed @patatahooligan's comment that adding noexcept to the Foo move constructor allows the code to compile in Clang and g++ 7.2.0. However, unless I am misreading the standard, it is not a requirement that a non-CopyInsertable T's move constructor be noexcept; in fact, the standard seems to allow for the possibility that a non-CopyInsertable T's move constructor can throw an exception, in which case it's not guaranteed whether there were effects.

UPDATE I have filed GCC Bugs 83981 and 83982.

like image 68
Daniel Trebbien Avatar answered May 14 '26 22:05

Daniel Trebbien



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!