If I delete the copy constructor and/or the copy-assignment constructor of a type Bar
,
struct Bar {
Bar() = default;
Bar(Bar const&) = delete;
};
std::optional<Bar>
is not copy-assignable.
using T = std::optional<Bar>;
static_assert(!std::is_copy_assignable_v<T>);
This is what I would expect. The same seems to be true for boost::optional<Bar>
, but unfortunately std::is_copy_assignable_v
evaluates to true.
using T = boost::optional<Bar>;
static_assert(std::is_copy_assignable_v<T>); // why doesn't this fail?
As a consequence, the following code does not compile:
template <typename T>
void foo() {
if constexpr (std::is_copy_assignable_v<T>){
T lhs;
T rhs;
lhs = rhs;
} else {
std::cout<< "Nope!\n";
}
}
int main()
{
foo<std::optional<Bar>>(); // works, prints "Nope!"
foo<boost::optional<Bar>>(); // compiler error
}
Compiler error with GCC 13.2:
In file included from /opt/compiler-explorer/libs/boost_1_84_0/boost/optional.hpp:15,
from <source>:4:
/opt/compiler-explorer/libs/boost_1_84_0/boost/optional/optional.hpp: In instantiation of 'void boost::optional_detail::optional_base<T>::construct(argument_type) [with T = Bar; argument_type = const Bar&]':
/opt/compiler-explorer/libs/boost_1_84_0/boost/optional/optional.hpp:277:20: required from 'void boost::optional_detail::optional_base<T>::assign(const boost::optional_detail::optional_base<T>&) [with T = Bar]'
/opt/compiler-explorer/libs/boost_1_84_0/boost/optional/optional.hpp:249:19: required from 'boost::optional_detail::optional_base<T>& boost::optional_detail::optional_base<T>::operator=(const boost::optional_detail::optional_base<T>&) [with T = Bar]'
/opt/compiler-explorer/libs/boost_1_84_0/boost/optional/optional.hpp:1099:15: required from 'void foo() [with T = boost::optional<Bar>]'
<source>:34:34: required from here
/opt/compiler-explorer/libs/boost_1_84_0/boost/optional/optional.hpp:410:8: error: use of deleted function 'Bar::Bar(const Bar&)'
410 | ::new (m_storage.address()) unqualified_value_type(val) ;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:19:5: note: declared here
19 | Bar(Bar const&) = delete;
| ^~~
Compiler returned: 1
https://godbolt.org/z/dd3r63KG8
Why does the code not work with boost::optional
?
I am writing a generic library that relies on the constexpr-if block with std::is_assignable_v<T>
. Is there an alternative type trait that I can use or write myself that reliably works for all types, including boost::optional
?
Edit: Jeff Garrett's answer links a six year old issue on the boost::optional Github page. So this is a known issue and a quick fix would be to include some special treatment for boost::optional
s in my library: https://godbolt.org/z/sdT6qdb4b
template <typename T>
struct is_copy_assignable : std::is_copy_assignable<T> {};
template <typename T>
struct is_copy_assignable<boost::optional<T>>
{
constexpr static bool value = std::is_copy_assignable_v<T> && std::is_copy_constructible_v<T>;
};
template <typename T>
constexpr bool is_copy_assignable_v = is_copy_assignable<T>::value;
It is an open issue: https://github.com/boostorg/optional/issues/54
There is not another trait one can use if the type itself advertises that it is copy-assignable.