I am trying to write a mechanism to detect if a type is a pointer like type. By that I mean it is dereferencable through operator*()
and operator->()
.
I have three different structs that are specialized accordingly:
is_pointer_like_dereferencable
which checks for operator*()
is_pointer_like_arrow_dereferencable
which checks for operator->()
is_pointer_like
which simply combines 1 & 2I added specializations for non-templated types like int, int*, ...
and for templated types like std::vector<...>, std::shared_ptr<...>, ...
.
However, it seems that I made a mistake when implementing is_pointer_like_arrow_dereferencable
. The relevant code is
template <typename T, typename = void>
struct is_pointer_like_arrow_dereferencable : std::false_type
{
};
template <typename T>
struct is_pointer_like_arrow_dereferencable<T, std::enable_if_t<
std::is_pointer_v<T> ||
std::is_same_v<decltype(std::declval<T>().operator->()), std::add_pointer_t<T>>>
> : std::true_type
{
};
template <template <typename...> typename P, typename T, typename... R>
struct is_pointer_like_arrow_dereferencable<P<T, R...>, std::enable_if_t<
std::is_same_v<decltype(std::declval<P<T, R...>>().operator->()), std::add_pointer_t<T>>>
> : std::true_type
{
};
template <typename T>
constexpr bool is_pointer_like_arrow_dereferencable_v = is_pointer_like_arrow_dereferencable<T>::value;
The 2nd struct should check if a non-templated type is either an actual pointer type or if the type does have an arrow operator that returns a pointer to itself. But when I test the mechanism with the code below, pointer types (like int*
are not detected correctly). Why is that?
template <typename T>
struct Test
{
T& operator*()
{
return *this;
}
T* operator->()
{
return this;
}
};
void main()
{
bool
a = is_pointer_like_arrow_dereferencable_v<int>, // false
b = is_pointer_like_arrow_dereferencable_v<int*>, // false, should be true
c = is_pointer_like_arrow_dereferencable_v<vector<int>>, // false
d = is_pointer_like_arrow_dereferencable_v<vector<int>*>, // false, should be true
e = is_pointer_like_arrow_dereferencable_v<Test<int>>, // true
f = is_pointer_like_arrow_dereferencable_v<Test<int>*>, // false, should be true
g = is_pointer_like_arrow_dereferencable_v<shared_ptr<int>>, // true
h = is_pointer_like_arrow_dereferencable_v<shared_ptr<int>*>, // false, should be true
i = is_pointer_like_arrow_dereferencable_v<int***>; // false
}
The is_pointer_like_dereferencable
struct only differs at the std::is_same_v<...>
part and it does detect actual pointer types correctly.
The fact that it fails to detect pointer types (which should be covered by std::is_pointer_v<...>
) doesn't make any sense to me. Can someone explain this?
But when I test the mechanism with the code below, pointer types (like int* are not detected correctly). Why is that?
S.F.I.N.A.E.: Substitution Failure Is Not An Error
So, for int*
, from decltype(std::declval<T>().operator->()
you get a substitution failure and the specialization isn't considered. So is used the general form, so std::false
You should write two specializations: one or pointers and one for operator->()
enabled classes.
Bonus answer: instead of type traits as is_pointer_like_arrow_dereferencable
(overcomplicated, IMHO), I propose you to pass through a set of helper functions (only declared)
template <typename>
std::false_type is_pointer_like (unsigned long);
template <typename T>
auto is_pointer_like (int)
-> decltype( * std::declval<T>(), std::true_type{} );
template <typename T>
auto is_pointer_like (long)
-> decltype( std::declval<T>().operator->(), std::true_type{} );
so is_pointer_like_arrow_dereferencable
can be simply written as a using
template <typename T>
using is_pointer_like_arrow_dereferencable = decltype(is_pointer_like<T>(0));
with helper is_pointer_like_arrow_dereferencable_v
template <typename T>
static auto const is_pointer_like_arrow_dereferencable_v
= is_pointer_like_arrow_dereferencable<T>::value;
The following is a full working example
#include <type_traits>
#include <iostream>
#include <memory>
#include <vector>
template <typename>
std::false_type is_pointer_like (unsigned long);
template <typename T>
auto is_pointer_like (int)
-> decltype( * std::declval<T>(), std::true_type{} );
template <typename T>
auto is_pointer_like (long)
-> decltype( std::declval<T>().operator->(), std::true_type{} );
template <typename T>
using is_pointer_like_arrow_dereferencable = decltype(is_pointer_like<T>(0));
template <typename T>
static auto const is_pointer_like_arrow_dereferencable_v
= is_pointer_like_arrow_dereferencable<T>::value;
template <typename T>
struct Test
{
T & operator* () { return *this; }
T * operator-> () { return this; }
};
int main()
{
std::cout << is_pointer_like_arrow_dereferencable_v<int>
<< std::endl, // false
std::cout << is_pointer_like_arrow_dereferencable_v<int*>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<std::vector<int>>
<< std::endl, // false
std::cout << is_pointer_like_arrow_dereferencable_v<std::vector<int>*>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<Test<int>>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<Test<int>*>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<std::shared_ptr<int>>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<std::shared_ptr<int>*>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<int***>
<< std::endl; // true
}