Reputation: 2282
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
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
Reputation: 59
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
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
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
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