5

Having trouble with SFINAE. I need to be able to determine if a Type has a member function operator-> defined regardless of its return type. Example follows.

This class in the tester. It defines operator->() with a return type of X*. I thus will not know what 'X' is to hardcode it everywhere.

template <class X>
class PointerX
{
    ...

    X* operator->() const;
    ...
}

This class tries to determines if the passed in T has a method operator-> defined; regardless of what operator-> return type is.

template<typename T>
struct HasOperatorMemberAccessor
{
    template <typename R, typename C> static R GetReturnType( R ( C::*)()const);

    template<typename U, typename R, R(U::*)()const> struct SFINAE{};
    template<typename U> static char Test(SFINAE<U,     decltype( GetReturnType(&U::operator->)),   &U::operator-> >*);
    template<typename U> static uint Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

This class is the exact same as above except that operator-> return type has to be 'Object'.

template<typename T>
struct HasOperatorMemberAccessorOBJECT
{
    template <typename R, typename C> static R GetReturnType( R ( C::*)()const);

    template<typename U, typename R, R(U::*)()const> struct SFINAE{};
    template<typename U> static char Test(SFINAE<U,     Object*,                &U::operator-> >*); // only change is we hardcoded Object as return type.
    template<typename U> static uint Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

Results:

void main()
{
    HasOperatorMemberAccessor<PointerX<Object>>::Test<PointerX<Object>>(0);         // fails  ::value is false;  Test => Test(...)

    HasOperatorMemberAccessorOBJECT<PointerX<Object>>::Test<PointerX<Object>>(0);       // works! ::value is true;   Test => Test(SFINAE<>*)  
}

HasOperatorMemberAccessor was unable to find PointX's member function "Object operator->() const". So it uses Test's generic version Test(...).

However, HasOperatorMemberAccessorOBJECT was able to find PointX's "Object operator->() const". Thus it uses Test specialized version Test(SFINAE*).

Both should have been able to find the "Object operator->() const" method; and thus both should use Test's specialized version Test(SFINAE*); and thus HasOperatorMemberAccessor>::value should be true for both.

The only difference between HasOperatorMemberAccessor and HasOperatorMemberAccessorOBJECT is that HasOperatorMemberAccessorOBJECT has the typename R hardcoded to object,

So the issue is that "decltype( GetReturnType(&U::operator->))" is not returning Object correctly. I've tried a number of different permitations of discovering the return type. They go as follows:

    decltype( GetReturnType(&U::operator->) )
    typename decltype( GetReturnType(&U::operator->))
    decltype( ((U*)nullptr)->operator->() )
    typename decltype( ((U*)nullptr)->operator->() )

None work, why? I'm using MSVC++ 10.0.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Michael G
  • 53
  • 4
  • One thing that strikes the eye is that `PointerX::operator->` returns `bool*`, not `bool`. – n. m. could be an AI Apr 26 '12 at 20:13
  • The type of X in PointerX doesn't matter as far as HasOperatorMemberAccessor is concerned. I tried to generalize my issue by not adding to many extraneous objects to the example. If it's too confusing, I will change bool to string. – Michael G Apr 26 '12 at 20:17
  • Let me try again.`PointerX::operator->` returns `Object*`. `decltype(...)` would be `Object*`. `HasOperatorMemberAccessorOBJECT` seemingly succeeds, although it replaces `decltype(...)` with `Object`. It looks like something is wrong here. I am not talking about `HasOperatorMemberAccessor` at all. – n. m. could be an AI Apr 26 '12 at 20:41

1 Answers1

4

Are you asking how to implement such a trait, or why decltype isn't behaving as you expect? If the former, here's one approach:

#include <type_traits>

template<typename T, bool DisableB = std::is_fundamental<T>::value>
struct HasOperatorMemberAccessor
{ 
private:
    typedef char no;
    struct yes { no m[2]; };

    struct ambiguator { char* operator ->() { return nullptr; } };
    struct combined : T, ambiguator { };
    static combined* make();

    template<typename U, U> struct check_impl;
    template<typename U>
    static no check(
        U*,
        check_impl<char* (ambiguator::*)(), &U::operator ->>* = nullptr
    );
    static yes check(...);

public:
    static bool const value=std::is_same<decltype(check(make())), yes>::value;
};

// false for fundamental types, else the definition of combined will fail
template<typename T>
struct HasOperatorMemberAccessor<T, true> : std::false_type { };

// true for non-void pointers
template<typename T>
struct HasOperatorMemberAccessor<T*, false> :
    std::integral_constant<
        bool,
        !std::is_same<typename std::remove_cv<T>::type, void>::value
    >
{ };

template<typename X>
struct PointerX
{
    X* operator ->() const { return nullptr; }
};

struct X { };

int main()
{
    static_assert(
        HasOperatorMemberAccessor<PointerX<bool>>::value,
        "PointerX<> has operator->"
    );
    static_assert(
        !HasOperatorMemberAccessor<X>::value,
        "X has no operator->"
    );
    static_assert(
        HasOperatorMemberAccessor<int*>::value,
        "int* is dereferencable"
    );
    static_assert(
        !HasOperatorMemberAccessor<int>::value,
        "int is not dereferencable"
    );
    static_assert(
        !HasOperatorMemberAccessor<void*>::value,
        "void* is not dereferencable"
    );
}

VC++ 2010 lacks the necessary C++11 facilities (e.g. expression SFINAE) needed to make this much cleaner.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • Well, this pretty much worked for me except that it disallows T to be a pointer type. I changed base to: struct base : conditional<!is_pointer::value,T,no>::type, base_mixin { }; That said, I would love to know why decltype didn't work. – Michael G Apr 26 '12 at 21:09
  • @MichaelG : `HasOperatorMemberAccessor<>` can be trivially specialized for pointers: `template class HasOperatorMemberAccessor : public std::true_type { };`. As for `decltype` not working as you expect, I didn't investigate your code much, but VC++2010's `decltype` implementation is pre-standard and buggy on top of it. – ildjarn Apr 26 '12 at 21:14
  • @Michael : Edited with proper pointer-type detection (`void*` was a gotcha). – ildjarn Apr 26 '12 at 21:27
  • `decltype` in VC++ 2010 won't work on void nor will it return the correct lvalue from a function when the expression is a dependent type. – Paul Fultz II Apr 27 '12 at 04:52
  • 1
    @Paul : `std::is_same` + `decltype` can be trivially replaced with `sizeof` if there are compiler deficiencies that my test cases don't cover. – ildjarn Apr 27 '12 at 05:20
  • @ildjarn `decltype` in your code should work since it doesnt return an lvalue or `void`. – Paul Fultz II Apr 27 '12 at 18:00
  • @Paul : Ah, apologies, I now realize your comment was directed at the OP. :-] – ildjarn Apr 27 '12 at 19:04