is_assignable and std::unique_ptr
Asked Answered
M

1

6

Here is a test file from gcc, live demo

struct do_nothing
{
    template <class T>
    void operator()(T*) {}
};

int
main()
{
    int i = 0;
    std::unique_ptr<int, do_nothing> p1(&i);
    std::unique_ptr<int> p2;
    static_assert(!std::is_assignable<decltype(p2), decltype(p1)>::value, ""); // note ! here.    
}

std::is_assignable

If the expression std::declval<T>() = std::declval<U>() is well-formed in unevaluated context, provides the member constant value equal true. Otherwise, value is false. Access checks are performed as if from a context unrelated to either type.

std::declval:

template<class T>
typename std::add_rvalue_reference<T>::type declval() noexcept;

The return type is T&& unless T is (possibly cv-qualified) void, in which case the return type is T.

Let's look at MoveAssignOnly:

struct MoveAssignOnly {
  MoveAssignOnly &operator=(MoveAssignOnly &) = delete;
  MoveAssignOnly &operator=(MoveAssignOnly &&) = default;
};

int main()
{
    static_assert(
    not std::is_assignable<MoveAssignOnly, MoveAssignOnly>::value, "");
}

live demo:

error: static_assert failed due to requirement '!std::is_assignable<MoveAssignOnly, MoveAssignOnly>::value'

Yes, it fails to compile because it provides a move assignment

Let's return to the gcc's test file and std::unique_ptr. As we know, std::unique_ptr also has move assignments.

However, unlike struct MoveAssignOnly, static_assert(!std::is_assignable<decltype(p2), decltype(p1)>::value, "");(more clearly, static_assert(!std::is_assignable<std::unique_ptr<int>, std::unique_ptr<int, do_nothing>>::value, ""); compiles happily.

I have struggled with libcxx's implementation of unique_ptr for long time, but still cannot figure out: how can std::unique_ptr be not assignable(! is_assignable) when std::unique_ptr provides move assignments?

Mumps answered 21/12, 2018 at 9:48 Comment(2)
std::unique_ptr is assignable.Lohr
Your assignment corresponds to line 2520, not line 2509 of memory header. Note this SFINAE applied in template parameters: class = _EnableIfDeleterAssignable<_Ep>.Ipomoea
P
4

p1 and p2 are of a different type. Unlike with shared_ptr, the deleter of a unique_ptr is part of the pointer's type. This means the move assignment operator does not allow you to assign (even move-assign) between two unique_ptrs if their deleter types differ.

unique_ptr also offers an assignment operator template which allows assigning from an rvalue of unique_ptr with a different deleter, but the deleters must be assignable (see reference). So you can make your static assert fire by making the deleters assignable:

struct do_nothing
{
    template <class T>
    void operator()(T*) {}

    template <class T>
    operator std::default_delete<T>() { return {}; }
};

int
main()
{
    int i = 0;
    std::unique_ptr<int, do_nothing> p1(&i);
    std::unique_ptr<int> p2;
    static_assert(!std::is_assignable<decltype(p2), decltype(p1)>::value, ""); // note ! here.    
}

[Live example]

Parkinson answered 21/12, 2018 at 9:51 Comment(2)
There is an assignment operator for std::unique_ptr that accepts different type of deleter, but that different deleter must be assignable into original deleter. See eel.is/c++draft/unique.ptr.single.asgn#6.3.Ipomoea
Thanks, you may be inserted with this related question: #53886038Mumps

© 2022 - 2024 — McMap. All rights reserved.