Joel Bodenmann
Joel Bodenmann

Reputation: 2282

Concept for smart pointers

Given a class template like this:

template<typename T>
struct foo
{
    T data;
};

How may one determine whether T is a smart pointer such as std::shared_ptr<T_underlying> using C++20 concepts?

I want to add functionality to foo<T> based on this criteria. For example, Instead of using SFINAE, I'd like to use the new concept system.

I want to achieve something like this:

template<typename T>
struct foo
{
    T data;

    void func()
    requires is_shared_ptr_v<T>
    {
        // ...
    }
};

Are there existing concepts for this in the STL? If not, I assume I can write a concept for std::shared_ptr, one for std::unique_ptr and so on and then tie them together with logical or in a general is_smart_pointer concept?

Upvotes: 3

Views: 2304

Answers (5)

Benjamin J&#228;hn
Benjamin J&#228;hn

Reputation: 1061

This solution might be more self explaining and can be adopted to support inherited smart pointer types easily. Improvements welcome!

#include <concepts>
#include <memory>
#include <type_traits>

template <typename Ptr>
concept SmartPointer = requires (Ptr p) {
    typename std::remove_cvref_t<Ptr>::element_type;
} && std::disjunction_v<
    std::is_same<std::remove_cvref_t<Ptr>, std::shared_ptr<typename std::remove_cvref_t<Ptr>::element_type>>,
    std::is_same<std::remove_cvref_t<Ptr>, std::weak_ptr<typename std::remove_cvref_t<Ptr>::element_type>>,
    std::is_same<std::remove_cvref_t<Ptr>, std::unique_ptr<typename std::remove_cvref_t<Ptr>::element_type>>>;




int main() {
    static_assert(SmartPointer<std::shared_ptr<int>>);
    static_assert(SmartPointer<std::shared_ptr<int>&>);
    static_assert(SmartPointer<std::shared_ptr<int>&&>);
    static_assert(SmartPointer<const std::shared_ptr<int>>);
    static_assert(SmartPointer<const std::shared_ptr<int>&>);
    static_assert(SmartPointer<std::unique_ptr<int>>);
    static_assert(SmartPointer<std::unique_ptr<int>&>);
    static_assert(SmartPointer<std::unique_ptr<int>&&>);
    static_assert(SmartPointer<const std::unique_ptr<int>>);
    static_assert(SmartPointer<const std::unique_ptr<int>&>);
    static_assert(SmartPointer<std::weak_ptr<int>>);
    static_assert(SmartPointer<std::weak_ptr<int>&>);
    static_assert(SmartPointer<std::weak_ptr<int>&&>);
    static_assert(SmartPointer<const std::weak_ptr<int>>);
    static_assert(SmartPointer<const std::weak_ptr<int>&>);
    static_assert(not SmartPointer<int>);
    static_assert(not SmartPointer<int*>);
    static_assert(not SmartPointer<int const *>);
    static_assert(not SmartPointer<int const * const>);
    static_assert(not SmartPointer<int * const>);
}

Upvotes: 1

In most cases, you need to know that the Pointer type is not just a pointer, but a pointer of a specific type, so I think it makes sense to add a basic pointer type. And the requirement to overload the operator is unnecessary, because, theoretically, it may not be present in some implementations of user-defined smart pointers.

template <typename TPointer,  typename T>
concept CPointerLike = requires (TPointer ptr)
{
    { *ptr } -> std::convertible_to<T>;
    { static_cast<bool>(ptr) };
};

static_assert(CPointerLike<int*, int>);
static_assert(!CPointerLike<int*, std::string>);
static_assert(CPointerLike<Derived*, Base>);
static_assert(CPointerLike<unique_ptr<int>, float>);
static_assert(CPointerLike<shared_ptr<float>, int>);

Upvotes: 1

Alex Vaskov
Alex Vaskov

Reputation: 248

Since by now I assume most of us can use C++20, I decided to post this C++20 version of what is essentially Adam Nevraumont's answer here, using concepts because of the added benefit of actually readable and helpful compiler error messages when things don't go as planned :)

#include <concepts>

template <typename Ptr>
concept pointer_like = std::is_pointer_v<Ptr> || requires (Ptr p) {
    { *p };
    { static_cast<bool>(p) };
    { p.operator->() } -> std::convertible_to<decltype( &*p )>;
};

Any suggestions on how to improve it are welcome

Upvotes: 4

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275800

Write "is pointer like". Has unary dereference, explicit cast to bool, and is either a pointer or has an operator arrow. Maybe that (for non pointers) get returns the same type as operator arrow.

Then state that a smart pointer is a pointer like thing that isn't a pointer.

Note that some iterators are smart pointers under this rule.

If you want an "owning" pointer you have to have a manual trait; there is no way to tell that other than semantically.

Upvotes: 5

Jarod42
Jarod42

Reputation: 218088

You might first create a traits to detect if type is a std::shared_ptr (way depends if you want to consider inheritance or not).

And then use the traits to build a concept:

template <typename T> struct is_shared_ptr : std::false_type {};
template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};

template <typename T> concept IsSharedPtr = is_shared_ptr<T>::value;

or

template <typename T>
std::true_type inherit_from_shared_ptr_impl(const std::shared_ptr<T>*);
std::false_type inherit_from_shared_ptr_impl(...);

template <typename T>
using inherit_from_shared_ptr =
    decltype(inherit_from_shared_ptr_impl(std::declval<T*>()));

template <typename T> concept InheritFromSharedPtr = inherit_from_shared_ptr<T>::value;

Upvotes: 6

Related Questions