Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SUGGESTION] optional-like API for raw-pointers (thanks to UFCS) #192

Closed
filipsajdak opened this issue Jan 1, 2023 · 6 comments
Closed

Comments

@filipsajdak
Copy link
Contributor

filipsajdak commented Jan 1, 2023

Thanks to UFCS, we can create an API that will make working with pointers more like with std::optional. That can make it easier to avoid dereferencing null pointers. Also, it can help create more generic code that will work with pointers and std::optional.

int* nptr = nullptr;
int* ptr = new int(42);

main: () -> int = {
    std::cout << "ptr.has_value(): "  << std::boolalpha << ptr.has_value() << std::endl;
    std::cout << "nptr.has_value(): " << std::boolalpha << nptr.has_value() << std::endl;

    std::cout << "ptr.value(): "      << ptr.value() << std::endl;
    std::cout << "ptr.value_or(0): "  << ptr.value_or(0) << std::endl;
    std::cout << "nptr.value_or(0): " << nptr.value_or(0) << std::endl;

    ptr.swap(nptr); std::cout <<"\nptr.swap(nptr);\n" << std::endl;

    std::cout << "ptr.value_or(0): "  << ptr.value_or(0) << std::endl;
    std::cout << "nptr.value_or(0): " << nptr.value_or(0) << std::endl;
    
    nptr.reset(); std::cout <<"\nnptr.reset();\n" << std::endl;

    std::cout << "nptr.has_value(): " << std::boolalpha << nptr.has_value() << std::endl;

    nptr.emplace(123); std::cout <<"\nnptr.emplace(123);\n" << std::endl;

    std::cout << "nptr.value_or(0): " << nptr.value_or(0) << std::endl;

    std::cout << "ptr.emplace(9999): " << ptr.emplace(9999) << std::endl;

}

template < typename X >
    requires std::is_pointer_v<X>
constexpr bool has_value( X const& x ) noexcept {
    return bool(x);
}

template < typename X >
    requires std::is_pointer_v<X>
constexpr auto value( X& x ) -> decltype(*x) {
    if (!x)
        throw std::bad_optional_access();
    return *x;
}

template < typename X >
    requires std::is_pointer_v<X>
constexpr auto value( X const& x ) -> decltype(*x) {
    if (!x)
        throw std::bad_optional_access();
    return *x;
}

template< typename X, class U >
    requires std::is_pointer_v<X>
constexpr auto value_or( X const& x, U&& default_value ) -> CPP2_TYPEOF(*x) {
    if (!x)
        return std::forward<U>(default_value);
    return *x;
}

template< typename X, class U >
    requires std::is_pointer_v<X>
constexpr auto swap( X& x, U& other ) -> void {
    std::swap(x, other);
}

template < typename X >
    requires std::is_pointer_v<X>
constexpr auto reset( X& x ) -> void {
    if (x) {
        delete x;
        x = nullptr;
    }
}

template< typename X, class... Args >
    requires std::is_pointer_v<X>
auto emplace( X& x, Args&&... args ) -> decltype(*x) {
    if (x)
        delete x;
    using T = CPP2_TYPEOF(*x);
    x = new T(std::forward<Args>(args)...);
    return *x;
}

template< typename X, class U, class... Args >
    requires std::is_pointer_v<X>
auto emplace( X& x, std::initializer_list<U> ilist, Args&&... args ) -> decltype(*x) {
    if (x)
        delete x;
    using T = CPP2_TYPEOF(*x);
    x = new T(ilist, std::forward<Args>(args)...);
    return *x;
}

The above code will return:

ptr.has_value(): true
nptr.has_value(): false
ptr.value(): 42
ptr.value_or(0): 42
nptr.value_or(0): 0

ptr.swap(nptr);

ptr.value_or(0): 0
nptr.value_or(0): 42

nptr.reset();

nptr.has_value(): false

nptr.emplace(123);

nptr.value_or(0): 123
ptr.emplace(9999): 9999

We can also make overloads for smart pointers.

@filipsajdak
Copy link
Contributor Author

Adding additional methods for unique_ptr:

template < typename X, typename D >
constexpr bool has_value( std::unique_ptr<X, D> const& x ) noexcept {
    return bool(x);
}

template < typename X, typename D >
constexpr auto value( std::unique_ptr<X, D>& x ) -> decltype(*x) {
    if (!x)
        throw std::bad_optional_access();
    return *x;
}

template < typename X, typename D >
constexpr auto value( const std::unique_ptr<X, D>& x ) -> decltype(*x) {
    if (!x)
        throw std::bad_optional_access();
    return *x;
}

template < typename X, typename D, class U >
constexpr auto value_or( std::unique_ptr<X, D> const& x, U&& default_value ) -> CPP2_TYPEOF(*x) {
    if (!x)
        return std::forward<U>(default_value);
    return *x;
}

template < typename X, typename D, class... Args >
auto emplace( std::unique_ptr<X, D>& x, Args&&... args ) -> decltype(*x) {
    using T = CPP2_TYPEOF(*x);
    x.reset(new T(std::forward<Args>(args)...));
    return *x;
}

Made the below code work as well:

uptr := new<int>(88);

std::cout << "uptr.has_value(): "  << std::boolalpha << uptr.has_value() << std::endl;
std::cout << "uptr.value(): "      << uptr.value() << std::endl;
std::cout << "uptr.value_or(0): "  << uptr.value_or(0) << std::endl;

uptr.reset(); std::cout <<"\nuptr.reset();\n" << std::endl;

std::cout << "uptr.value_or(0): "  << uptr.value_or(0) << std::endl;

uptr.emplace(123); std::cout <<"\nuptr.emplace(123);\n" << std::endl;
std::cout << "uptr.emplace(9999): " << uptr.emplace(9999) << std::endl;

std::cout << "uptr.value_or(0): "  << uptr.value_or(0) << std::endl;

@jcanizales
Copy link

To me, anything that makes the "optional<T&> is better than T*" debate disappear, is God's work.

@hsutter
Copy link
Owner

hsutter commented Jan 4, 2023

Thanks for the suggestion. One question I have is that the Cpp2 is facility is already doing a similar unification, but instead of going towards optional's interface it's instead unifying divergent APIs under is (and as). For example,

  • optional has the .has_value() and nullopt mentioned above, variant has monostate and .valueless_by_exception, pointers have nullptr, and Cpp2's current direction is to unify those under is void aka is cpp2::empty.
  • I think the line ptr.swap(nptr); already works for std::swap thanks to UFCS? (If there's a using std::swap; it works as written, otherwise it should work now as ptr.std::swap(nptr);.)

So I think I'd prefer to stick with unifying under is and as, rather than under optional's interface. (value_or could be a general helper that builds on stop of is empty, right? Then it would work on anything that supports is, not just optional and pointers?)

If you still feel strongly about pursuing this suggestion, could you recast it in the form of the suggestion issue template please? In particular, is this something that's proven that we already teach people to do in today's C++ guidance, and so we'd be making a known best practice the default? What current guidelines would it let us no longer teach, or make easier to teach?

Thanks!

@hsutter
Copy link
Owner

hsutter commented Jan 4, 2023

@filipsajdak I see you thumbs-up'd my comment, so I'll close this as resolved for now -- if you're planning to pursue it with the suggestion issue template's additional information just go ahead and create it as a new issue with the additional info and rationale. Thanks!

@hsutter hsutter closed this as completed Jan 4, 2023
@filipsajdak
Copy link
Contributor Author

Yes, close it. I found that and shared it. If I find more facts to back it up, I will repost it with the proper template.

@JohelEGP
Copy link
Contributor

  • otherwise it should work now as ptr.std::swap(nptr);

Should it?
GCC rejects: https://compiler-explorer.com/z/M6M71dnb9.

UFCS shouldn't work on a qualified name (although only GCC seems to reject that case).
-- #307 (comment) (extract)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants