c++boosttype-traitsstdoptionalboost-optional

Unexpected behavior of std::is_copy_assignable and boost::optional


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::optionals 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;

Solution

  • 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.