15

I think about overloading std::is_pointer in C++11 to yield true for std::shared_ptr<T> as well, since the latter behaves very much as a T*.

#include <type_traits>

namespace std {

template <typename T> struct is_pointer<shared_ptr<T>> : std::true_type {};
template <typename T> struct is_pointer<shared_ptr<T const>> : std::true_type {};

}

I wonder why this overload has not already been included in the standard implementation. Is there a pitfall that I'm overlooking?

As an alternative one could of course introduce a new trait is_shared_ptr<T>.

Actually, I tried the following code in the first place:

template <typename T> 
struct is_pointer<shared_ptr<typename std::remove_cv<T>::type>>
  : std::true_type 
{};

which does not compile with GCC 4.7 due to

error: template parameters not used in partial specialization:
error:         ‘T’
Morwenn
  • 21,684
  • 12
  • 93
  • 152
Erwin411
  • 734
  • 1
  • 7
  • 14
  • 3
    `is_pointer` is useful in template programming when I want to know if something is a raw pointer type, not just like a pointer. You could implement an `is_like_ptr` which does things like test with SFINAE if `*p` and `++p` are valid expressions. – aschepler Jun 12 '13 at 12:48
  • 3
    @aschepler: Smart pointers don't usually support arithmetic. `is_dereferencable`, just checking for `*p`, might be more appropriate. – Mike Seymour Jun 12 '13 at 13:12
  • Good point. I don't think I'm awake yet. – aschepler Jun 12 '13 at 13:28
  • It would surprise me that `is_pointer_t` does not imply that I can assign a `T` to a `void *`. – nwp Jun 19 '15 at 11:43
  • Also, I would expect I can safely memcpy()/memset() raw pointers. With smart pointers, this is not the case. – Sebastian Mach Jan 21 '16 at 16:29
  • @Erwin411 the reason why your attempted `is_pointer` does not work is: template specialisations are just matched against what is there at the usage site. The compiler will never actively extend anything at the usage site in order to generate a match. For this reason, the `remove_cv` gets into the way and prevents a match – Ichthyo Jul 31 '16 at 11:29

4 Answers4

13

std::is_pointer somehow comes from Boost and is originally meant to detect raw pointers and function pointers only, here is the bottom note you can find in Boost documentation:

is_pointer detects "real" pointer types only, and not smart pointers. Users should not specialise is_pointer for smart pointer types, as doing so may cause Boost (and other third party) code to fail to function correctly. Users wanting a trait to detect smart pointers should create their own. However, note that there is no way in general to auto-magically detect smart pointer types, so such a trait would have to be partially specialised for each supported smart pointer type.

They probably just had it behave like that in the standard library for compatibility to keep a low level of surprise for users that already used it. Anyway, as you just demonstrated it, it is easy to create your own trait to detect smart pointers.

Basically, what you are looking would be a trait that would look for types which implement the Dereferenceable concept (even though this one would also work for std::optional and not only pointers/smart pointers).

And for the sake of completeness, Boost's is_pointer design is there to detect only raw pointers and not pointer-like classes. But the other answers should already give you some pretty good information about that.

Morwenn
  • 21,684
  • 12
  • 93
  • 152
  • 4
    I think `Dereferencable` concept is more appropiate, since smart pointers are not incrementable, decrementable and all the other stuff that qualifies random access iterators. – Arne Mertz Jun 12 '13 at 13:05
  • I wouldn't say it's just for *boost*-compatibility, but due to the inherent design of all those language-level type category identifiers. They are designed ot identify actual type-categories of variables in contrast to higher-level semantic concepts. – Christian Rau Jun 12 '13 at 13:23
  • @ChristianRau I was tempted to add something along those lines at first, with some "can you easily implement a trait for raw pointers?", but the answer was "yes" and you had already answered by the time, so I just let like that. – Morwenn Jun 12 '13 at 13:27
  • wrt `boost::optional` (or `std::optional` in C++14), that is a kind of smart pointer as well. The "smart" part in "smart pointer" does not necessarily refer to object lifetime management and ownership semantics. Iterators are smart pointers (smart increment/decrement), and `optional` is a smart pointer with stack based object lifetime management, smart initialization and smart null detection. – Arne Mertz Jun 12 '13 at 13:30
  • @ArneMertz I can't find any documentation about what a pointer concept would be. Some documentation would b good for pedantry. – Morwenn Jun 12 '13 at 13:34
  • Thanks for reminding me of the pointer arithmetics which I overlooked. The suggestion of a trait `is_dereferencable` would indeed fit my purpose much better. But I can't find it in the standard library nor in Boost. Does it mean that the `Dereferencable` concept exists, but I need to implement the type trait myself? – Erwin411 Jun 12 '13 at 14:45
  • `boost::has_dereference` did the trick. So my final code for the function return type reads `typename std::enable_if::type::value, void>::type`. Many thanks to all for the insightful answers. – Erwin411 Jun 12 '13 at 16:27
  • @Erwin411 No problem, but you really have to take in account the other answers too; some of them provide good guidelines about standard trait classes :) – Morwenn Jun 12 '13 at 19:27
11

I think about overloading std::is_pointer in C++11 to yield true for std::shared_ptr as well, since the latter behaves very much as a T*.

It does not. Good luck doing ++p or p[i] with a shared_ptr.

I wonder why this overload has not already been included in the standard implementation. Is there a pitfall that I'm overlooking?

It was not included because it would have been simply wrong: is_pointer tells you if a type is a pointer type (§3.9.2). shared_ptr is not a pointer type, so have is_pointer<shared_ptr<int>::value be true would simply be wrong.

Actually, I tried the following code in the first place:

template <typename T> 
struct is_pointer<shared_ptr<typename std::remove_cv<T>::type>>
  : std::true_type 
{};

What the hell is remove_cv doing there? The following "works" in GCC.

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

However, it has undefined behaviour. In general, you are not allowed to add specializations to templates in namespace std if they don't agree with the defined semantics. The semantics for std::is_pointer<T> are such that it only derives from std::true_type if T is a pointer type which std::shared_ptr isn't. That alone would be enough, but in this particular case the standard actually goes to the length of forbidding it explicitly (§20.9.2):

The behavior of a program that adds specializations for any of the class templates defined in this subclause is undefined unless otherwise specified.

The only template from <type_traits> that a user can add specializations to is std::common_type.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • +1 For mentioning that specializing `` classe templates (except `common_type`) is illegal. Unfortunately, I've seen even experienced programmers suggesting to do this! Obrigado. – Cassio Neri Jun 12 '13 at 15:29
  • 1
    C++17 structured bindings also now expects the user to specialise ``. – user2023370 Feb 25 '17 at 23:04
6

While I agree that more general type traits, like behaves_like_pointer (excuse the stupid name), is_callable (for all things having ()) or is_indexable (for array-like things) would be very useful, that is certainly not what things like is_pointer, is_function or is_array have been designed for. They are traits identifying actual hard language type categories, much the same like is_integral, is_class or is_rvalue_reference.

So overlaoding is_pointer for any kind of smart pointer would conradict that original purpose, and that purpose is a clear and unambigous one. But I still agree that additional more general conceptual type categories like is_smart_pointer, is_callable, is_nothrow_swappable or is_hashable would be very useful, too.

Christian Rau
  • 45,360
  • 10
  • 108
  • 185
0

Other answers covered the technical details and some of the background.

The idea of type_traits in std:: is to provide primitives. You use those out of the box, and do specializations only to cover your self-defined types to follow get reports correctly for the original semantics.

What you want really (IMO) is not diverting the is_pointer template, but to have a query function that covert your semantics. So that is what you should do: your own is_maybesmart_pointer<> that reports true for original pointers and whatever else you desire. And use that in your code.

Tweaking the original by the original idea would quite possibly lead to ODR violation as how do you know the libs you link did not already use is_pointer with a shared_ptr or something?

Balog Pal
  • 16,195
  • 2
  • 23
  • 37