DoZerg
DoZerg

Reputation: 323

Does std::unique_ptr support fancy pointers?

I assume "fancy pointers too", because for resources like files/sockets, fancy pointers can be a great tool. And there seems no reason std::unique_ptr shouldn't support them.

There are different ways to test empty-ness of the managed pointer in std::unique_ptr (GCC):

The problem is that they have different requirements to the pointer type:

The inconsistent usage means all of them are needed, while one of them would suffice semantically.

#include <memory>

using namespace std;

class File {
    FILE * pf_ = nullptr;
public:
    File() = default;
    File(const char * pathname) { /*...*/ };
    void Close() { /*...*/ };

    // All are needed to make std::unique_ptr work
    explicit operator bool() const { return pf_ == nullptr; }
    bool operator ==(nullptr_t) const { return pf_ == nullptr; }
    bool operator ==(File) const { /*...*/ return true; };
};

struct FileCloser {
    using pointer = File;
    void operator ()(File p) const { p.Close(); }
};

int main() {
    using FilePtr = unique_ptr<File, FileCloser>;
    FilePtr p(File("./abc"));
    if(p)
        p.reset();
}

https://godbolt.org/z/fjPjWzzhW

IMHO, std::unique_ptr should require minimum from the pointer type. One solution is to add a static member, e.g. __test_p, and use it to test empty-ness consistently:

template<typename T, typename D = std::default_delete<T>>
class unique_ptr
{
// ...
private:
    static constexpr bool __test_p(pointer p) noexcept {
        if constexpr (is_convertible_v<pointer, bool>)
            return bool(p);
        else if constexpr (requires {{ p != nullptr} -> convertible_to<bool>; })
            return p != nullptr;
        else
            return p != pointer();
    }
// ...
};

Upvotes: 1

Views: 156

Answers (1)

Caleth
Caleth

Reputation: 63142

Can std::unique_ptr manage pointers only, or fancy pointers (pointer-like types) too?

Yes

The requirements for std::unique_ptr<T, Deleter> are:

  • Deleter must be FunctionObject or lvalue reference to a FunctionObject or lvalue reference to function, callable with an argument of type unique_ptr<T, Deleter>::pointer.

  • std::remove_reference_t<Deleter>::pointer, if it exists, must satisfy NullablePointer.

Among other things, a NullablePointer must satisfy all three of the requirements you claim are inconsistent, and they must all be consistent with each other.

Why are there inconsistent ways to test empty-ness of the managed pointer?

Having "redundant" operations is a trade off in favour of users of a constraint against implementors of a constraint, it is not "wrong".

While it is true that operator bool must be equivalent to ptr != nullptr, and ptr == nullptr must be equivalent to ptr == pointer(nullptr), it's more ergonomic to have operator bool and all the comparisons. By ensuring that every NullablePointer can do every operation that raw pointers can, code can be written expecting raw pointers, and "just work" for fancy pointers.

IMHO, std::unique_ptr should require minimum from the pointer type.

That's your opinion. The committee chose otherwise.

Upvotes: 6

Related Questions