7

In C++03, how do I determine if a type T is dereferenceable?
By which I mean, how do I statically determine if *t would be a valid expression for t of type T?

My attempt:

template<bool B, class T = void> struct enable_if { };
template<class T> struct enable_if<true, T> { typedef T type; };

unsigned char (&helper(void const *))[2];
template<class T>
typename enable_if<
    !!sizeof(**static_cast<T *>(NULL)),
    unsigned char
>::type helper(T *);

template<class T>
struct is_dereferenceable
{ static bool const value = sizeof(helper(static_cast<T *>(NULL))) == 1; };

struct Test
{
    int *operator *();
    void operator *() const;
private:
    Test(Test const &);
};

int main()
{
    std::cout << is_dereferenceable<int *>::value;       // should be true
    std::cout << is_dereferenceable<void *>::value;      // should be false
    std::cout << is_dereferenceable<Test>::value;        // should be true
    std::cout << is_dereferenceable<Test const>::value;  // should be false
}

It works on GCC (prints 1010) but crashes and burns on VC++ (1110) and Clang (1111).

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    I wouldn't invest much hope in the ability of VC++ to do template metaprogramming. As for clang, it thinks `&*foo` is OK when `foo` is `void`, even without any templates. – n. m. could be an AI Dec 30 '13 at 06:39
  • @n.m.: Uh what does `&*foo` have to do with anything though? I'm not taking the address of anything here... – user541686 Dec 30 '13 at 06:44
  • I'm talking about what clang thinks on dereferencing a void*, not about anything in your code. – n. m. could be an AI Dec 30 '13 at 06:47
  • @n.m.: But I mean clang certainly doesn't think `sizeof(*foo)` is okay when `foo` is `void*` right? I'm not sure I see any relationship between the two issues... – user541686 Dec 30 '13 at 06:48
  • @n.m.: Also how can you even have a `foo` declared as `void` to begin with? I assumed you mean `void*` instead. – user541686 Dec 30 '13 at 06:49
  • In fact clang thinks sizeof(void) is perfectly OK. Perhaps it's a extension you can disable. You cannot declare a void variable with clang, but I don't see where your template code does that. You can have a void value instead by, well, dereferencing a void*. – n. m. could be an AI Dec 30 '13 at 06:56
  • Doesn't this question amount to asking how to implement `is_pointer<>`? ( e.g. http://stackoverflow.com/questions/3177686/how-to-implement-is-pointer and albeit with a `!is_same`) – Tony Delroy Dec 30 '13 at 07:43
  • @TonyD Not quite, because you can have a user-defined `operator*()`, – n. m. could be an AI Dec 30 '13 at 07:49
  • @n.m. teach me not to look at the question carefully... ;-) Cheers. – Tony Delroy Dec 30 '13 at 08:00
  • Why std::cout << is_dereferenceable::value should be false? void operator *() const is accessible. – sliser Dec 30 '13 at 09:23
  • @sliser: Because it returns `void` and hence cannot be called. – user541686 Dec 30 '13 at 09:28
  • It can be called, you just cannot use the return value. – n. m. could be an AI Dec 30 '13 at 09:36
  • Assume T = Test const, t instance of it, so you could legally dereference t – sliser Dec 30 '13 at 09:37
  • n.m., @sliser: Oops yes you're correct, `operator *()` can be called, my bad. I'd still consider it 'not dereferenceable' because it doesn't return anything. But, if you have an answer that is otherwise correct and only has trouble with this issue, feel free to post it, I'd still probably accept it since this is really a debatable edge case. – user541686 Dec 30 '13 at 10:04
  • If you are willing to ignore the `operator*` issue, you can just specialize for `void*` (and probably with cv qualifiers too). If you additionally need `operator*` callable, regardless of return type, use the SFINAE shtick with `sizeof(t.operator*(),0)`. – n. m. could be an AI Dec 30 '13 at 10:18
  • @n.m.: Wow, yeah, that seems to work... it didn't occur to me to just call operator*() directly without using the return value! Would you like to add that to your answer, along with an example of it in your answer? For some reason I can't figure out how to do this in an SFINAE context (it always seems to be in the wrong context so it compile errors for some reason). If it works I'll probably accept it. :) – user541686 Dec 30 '13 at 10:42
  • don't forget about global operator * – sliser Dec 30 '13 at 10:52
  • @sliser: What's the global `operator *`? – user541686 Dec 30 '13 at 11:43
  • @Mehrdad i mean non-member operators. e.g. `struct A {}; void operator *(A)` – sliser Dec 30 '13 at 11:52
  • @sliser: That's possible?! I've never seen that! – user541686 Dec 30 '13 at 12:13
  • @Mehrdad http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B – sliser Dec 30 '13 at 12:18

2 Answers2

3
#include <boost\type_traits\remove_cv.hpp>
#include <boost\type_traits\is_same.hpp>
#include <boost\type_traits\remove_pointer.hpp>
#include <boost\type_traits\is_arithmetic.hpp>
#include <boost\utility\enable_if.hpp>

namespace detail
{
    struct tag 
    { 
        template < typename _T > 
        tag(const _T &); 
    };

    // This operator will be used if there is no 'real' operator
    tag operator*(const tag &);

    // This is need in case of operator * return type is void
    tag operator,(tag, int);  

    unsigned char (&helper(tag))[2];

    template < typename _T >
    unsigned char helper(const _T &);

    template < typename _T, typename _Enable = void >
    struct traits
    {
        static const bool value = (sizeof(helper(((**static_cast <_T*>(NULL)), 0))) == 1);
    };

    // specialization for void pointers
    template < typename _T >
    struct traits < _T,
        typename boost::enable_if < typename boost::is_same < typename boost::remove_cv < typename boost::remove_pointer < _T > ::type > ::type, void > > ::type >
    {
        static const bool value = false;
    };

    // specialization for arithmetic types
    template < typename _T >
    struct traits < _T,
        typename boost::enable_if < typename boost::is_arithmetic < typename boost::remove_cv < _T > ::type > > ::type >
    {
        static const bool value = false;
    };
}

template < typename _T > 
struct is_dereferenceable :
    public detail::traits < _T >
{ };

I have tested it in msvs 2008

sliser
  • 1,645
  • 11
  • 15
1

It's a bug in clang. Somebody should file a report.

Clang implements the old gcc extension of doing pointer arithmetic on void pointers. This includes being able to dereference a void pointer, and to take sizeof(void). Clang does so because people used to complain about old code compilable with gcc but not clang. So clang maintainers went ahead and implemented this extension. The problems here are many-fold:

  1. With gcc, the extension always worked silently with the C compiler, but produced a warning with the C++ compiler. With clang, both C and C++ compilers are silent by default if the extension is used.
  2. With g++, sizeof(void) produces a substitution failure when used in a template, despite being only a warning. With clang++, it does not.
  3. You can disable the extension in clang++ with -Wpointer-arith -Werror, but this leads to sizeof(void) being a compilation error instead of a substitution failure.

I cannot vouch for VC++.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243