SFINAE for operator->

E

edd

Hello all,

Is there a way to determine whether a particular type supports the ->
operator at compile time?
I'm trying to write a template function (or a series of overloads)
that will yield the raw pointer at "the end of the arrow". For types
that support the operator, I have a reasonable solution, but I'd like
to have a null pointer returned if the operator isn't supported by a
particular object.

So far I have:

//----begin----
#include <memory>
#include <iostream>

template<typename Ptr, typename T>
Ptr pointer_from_arrowable(T *p) { return p; }

template<typename Ptr, typename Arrowable>
Ptr pointer_from_arrowable(Arrowable &a)
{
return pointer_from_arrowable<Ptr>(a.operator->());
}

int main()
{
std::auto_ptr<int> ap(new int(5));
int *p = pointer_from_arrowable<int *>(ap);
std::cout << *p << '\n'; // 5
return 0;
}
//----end----

I thought I might be able to use SFINAE to help the compiler choose an
overload that returns 0 when no operator-> exists, but I end up with
errors (MSVC8 and MinGW g++ 3.4.5) about ambiguous overloads (see end
of post):

//----begin----
#include <memory>
#include <iostream>

template<typename T>
struct mfn_pointer { typedef void (T::*type)(); };

template<typename Ptr, typename T>
Ptr pointer_from_arrowable(T *p) { return p; }

template<typename Ptr, typename Arrowable>
Ptr pointer_from_arrowable(
Arrowable &a,
typename mfn_pointer<Arrowable>::type sfinae =
(typename
mfn_pointer<Arrowable>::type)&Arrowable::eek:perator->
)
{
return pointer_from_arrowable<Ptr>(a.operator->());
}

template<typename Ptr, typename Arrowable>
Ptr pointer_from_arrowable(Arrowable &a) { return 0; }

int main()
{
std::auto_ptr<int> ap(new int(5));
int *p = pointer_from_arrowable<int *>(ap);
std::cout << *p << '\n'; // 5
return 0;
}
//----end----

So is there any way to do what I want?

Here are the compiler errors I got when I attempted the SFINAE code:

MinGW g++ 3.4.5:
--------------------------------------------------
g++ -o arrow.o -c arrow.cpp -ggdb3 -Wall -Wextra -pedantic -ansi -O0 -
Wswitch -D _GLIBCXX_DEBUG
arrow.cpp: In function `int main()':
arrow.cpp:26: error: call of overloaded
`pointer_from_arrowable(std::auto_ptr<int>&)' is ambiguous
arrow.cpp:16: note: candidates are: Ptr
pointer_from_arrowable(Arrowable&, typename
mfn_pointer<Arrowable>::type) [with Ptr = int*, Arrowable =
std::auto_ptr<int>]
arrow.cpp:21: note: Ptr
pointer_from_arrowable(Arrowable&) [with Ptr = int*, Arrowable =
std::auto_ptr<int>]
error: system call returned unexpected exit-code 1
--------------------------------------------------

MSVC 8:
--------------------------------------------------
cl /Foarrow.obj /c arrow.cpp /nologo /Od /Zc:forScope,wchar_t /RTCc /
GR /RTCs /Zi /wd4996 /D _CRT_SECURE_NO_DEPRECATE /RTCu /EHsc /MTd /W3
arrow.cpp
arrow.cpp(26) : error C2668: 'pointer_from_arrowable' : ambiguous call
to overloaded function
arrow.cpp(21): could be 'Ptr
pointer_from_arrowable<int*,std::auto_ptr<_Ty>>(Arrowable &)'
with
[
Ptr=int *,
_Ty=int,
Arrowable=std::auto_ptr<int>
]
arrow.cpp(11): or 'Ptr
pointer_from_arrowable<int*,std::auto_ptr<_Ty>>(Arrowable &,void
(__thiscall std::auto_ptr<_Ty>::* )(void))'
with
[
Ptr=int *,
_Ty=int,
Arrowable=std::auto_ptr<int>
]
while trying to match the argument list '(std::auto_ptr<_Ty>)'
with
[
_Ty=int
]
error: system call returned unexpected exit-code 2
 
D

dasjotre

Hello all,

Is there a way to determine whether a particular type supports the ->
operator at compile time?
I'm trying to write a template function (or a series of overloads)
that will yield the raw pointer at "the end of the arrow". For types
that support the operator, I have a reasonable solution, but I'd like
to have a null pointer returned if the operator isn't supported by a
particular object.
<snipped>

assuming T is something like

template<class T>
struct my_ptr
{
typedef T element_type; // like std::auto_ptr
element_type * operator->() const;
};

a possible solution would be (tried only on VC8)

template<class T>
struct has_arrowop
{
typedef char (&yes)[1];
typedef char (&no)[2];

template<class T>
static yes foo(T *, typename T::element_type * (T::*pt)() const =
&T::eek:perator->);

template<class T>
static no foo(T const *, bool=false);

// use the one your compiler supports
//enum { value = sizeof(foo((T const *)0, true)) !=
sizeof(foo((T*)0)) };
static const bool value = sizeof(foo((T const *)0, true)) !=
sizeof(foo((T*)0));
};

#include <memory>
#include <iostream>

struct A {};

struct B
{
typedef int element_type;
element_type * operator->() const;
};

int main()
{
std::cout << "A : " << has_arrowop<A>::value << "\n";
std::cout << "B: " << has_arrowop<B >::value <<'\n';
std::cout << "std::auto_ptr<A> : " <<
has_arrowop<std::auto_ptr<A> >::value << "\n";

return 0;
}

DS
 
E

edd

Hi DS,

Thanks for your reply.

On 3 Jun, 19:54, (e-mail address removed) wrote:> Hello all,
a possible solution would be (tried only on VC8)

template<class T>
struct has_arrowop
{
typedef char (&yes)[1];
typedef char (&no)[2];

template<class T>
static yes foo(T *, typename T::element_type * (T::*pt)() const =
&T::eek:perator->);

template<class T>
static no foo(T const *, bool=false);

// use the one your compiler supports
//enum { value = sizeof(foo((T const *)0, true)) !=
sizeof(foo((T*)0)) };
static const bool value = sizeof(foo((T const *)0, true)) !=
sizeof(foo((T*)0));

};

To keep MinGW g++ happy, I had to parameterise the foo() taking an
element_type pointer by a type named U rather than T (which is the
same as the class parameter type). I also made the other foo a non-
template function, because I don't believe it needs the type
parameter.

But with or without these changes, it doesn't inherently check for
operator->, unless I'm mistaken. Instead, a type is removed from
overload resolution if it doesn't have a nested element_type.

For example, the following type gives a compiler error when used with
has_arrowop<>:

struct X
{
typedef int element_type;
};

In other words, the existence of operator-> is never checked unless an
element_type exists. If it does exist, it is assumed that an operator-
is also supplied, which it may not be in cases such as X, above.

Am I interpreting your code correctly?

Perhaps a little more context may help.

I'm actually trying to deduce if a general iterator type supports
operator->.

For output iterators, I can assume not since even though it may be
supplied by a given type of output iterator, it is not a requirement
as far as the standard is concerned.

For all other types of iterator (input, forward, bidirectional and
random access) I thought I might be able to use your trick by
replacing T::element_type with std::iterator_traits<T>::pointer, but
this won't work as every iterator type is *required* to have such a
type defined (I believe) and so sfinae gives me nothing here.

Further more, I cannot just assume that all non-input iterators will
supply an operator-> because that's not the case. They only need
provide one if the expression (*p).m is well formed for an iterator p
and some member m. std::istreambuf_iterator is an example of an input
iterator that doesn't provide an operator->.

So what I've done is assume that the following iterator's don't supply
an operator:
- output iterators
- iterators whose value_type is of primitive type

This may be sufficient, but I haven't convinced myself of that quite
yet.

If you have any more thoughts, *please* let me know :)

Thanks again for your response!

Edd
 
D

dasjotre

Hi DS,

Thanks for your reply.

On 3 Jun, 19:54, (e-mail address removed) wrote:> Hello all,
a possible solution would be (tried only on VC8)
template<class T>
struct has_arrowop
{
typedef char (&yes)[1];
typedef char (&no)[2];
template<class T>
static yes foo(T *, typename T::element_type * (T::*pt)() const =
&T::eek:perator->);
template<class T>
static no foo(T const *, bool=false);
// use the one your compiler supports
//enum { value = sizeof(foo((T const *)0, true)) !=
sizeof(foo((T*)0)) };
static const bool value = sizeof(foo((T const *)0, true)) !=
sizeof(foo((T*)0));

To keep MinGW g++ happy, I had to parameterise the foo() taking an
element_type pointer by a type named U rather than T (which is the
same as the class parameter type). I also made the other foo a non-
template function, because I don't believe it needs the type
parameter.

But with or without these changes, it doesn't inherently check for
operator->, unless I'm mistaken. Instead, a type is removed from
overload resolution if it doesn't have a nested element_type.

For example, the following type gives a compiler error when used with
has_arrowop<>:

struct X
{
typedef int element_type;

};

In other words, the existence of operator-> is never checked unless an
element_type exists. If it does exist, it is assumed that an operator-
is also supplied, which it may not be in cases such as X, above.

yes, the element_type is what kicks in SFINAE, not the
existence of operator->. changing to

template<class T, class U>
static yes foo(T *, U * (T::*pt)() const = &T::eek:perator->);

doesn't help. the default parameter is never considered for SFINAE.

the whole thing is useless :-(
Am I interpreting your code correctly?

better than me.
Perhaps a little more context may help.

I'm actually trying to deduce if a general iterator type supports
operator->.

For output iterators, I can assume not since even though it may be
supplied by a given type of output iterator, it is not a requirement
as far as the standard is concerned.

For all other types of iterator (input, forward, bidirectional and
random access) I thought I might be able to use your trick by
replacing T::element_type with std::iterator_traits<T>::pointer, but
this won't work as every iterator type is *required* to have such a
type defined (I believe) and so sfinae gives me nothing here.

Further more, I cannot just assume that all non-input iterators will
supply an operator-> because that's not the case. They only need
provide one if the expression (*p).m is well formed for an iterator p
and some member m. std::istreambuf_iterator is an example of an input
iterator that doesn't provide an operator->.

So what I've done is assume that the following iterator's don't supply
an operator:
- output iterators
- iterators whose value_type is of primitive type
This may be sufficient, but I haven't convinced myself of that quite
yet.

I'm not very good with the 'chapter and verse' (take
this with a pinch of salt) but I think that all iterators
must provide operator *, input as rvalue and
output as lvalue and all input iterators have to provide
operator-> as well.

the original SGI STL models all iterators, both
input and output by their TrivialIterator model
which requires operator->
If you have any more thoughts, *please* let me know :)

check:
http://www.boost.org/libs/iterator/doc/pointee.html
and BOOST_MPL_HAS_XXX_TRAIT_DEF

not exactly what you need but you might find
it useful anyway.

good luck ;)

DS.
 
D

dasjotre

the original SGI STL models all iterators, both
input and output by their TrivialIterator model
which requires operator->

Actually, that is not correct. only input iterators
are modelled by TrivialIterator.
 
D

dasjotre

Actually, that is not correct. only input iterators
are modelled by TrivialIterator.

( doh! )
and obviously only for iterators where (*it).member
is well formed.
 
E

edd

( doh! )
and obviously only for iterators where (*it).member
is well formed.

This is the catch! So the half-solution I'm currently using is to
assume that an iterator doesn't have an operator-> if either:

(a) it's a pointer (in which case -> can be applied directly)
(b) std::iterator_traits<X>::iterator_category is std::eek:utput_iterator
(c) std::iterator_traits<X>::value_type is a primitive type

If an iterator's value type is primitive, operator-> doesn't have to
exist because (*x).m doesn't make sense for any such iterator x in
combination any member name, m.

The case that I can't handle is if a non-output iterator's value type
is non-primitive. Here I always assume that an operator-> does exist,
but this may not be the case in some very rare and strange examples.

I think those rare and strange cases are so rare and strange that in
practice I don't have to worry. But I would still like a solution with
100% coverage :/

You can perhaps imagine some half-arsed class that acts like a
primitive numerical type and whose interface is implemented entirely
in terms of non-member operators.

Edd
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,816
Latest member
SapanaCarpetStudio

Latest Threads

Top