Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

has_foo revisited...

0 views
Skip to first unread message

rune...@yahoo.com

unread,
May 1, 2006, 6:18:00 PM5/1/06
to
Hello,
I'd like to have a foo function that handles primitives and classes in
a seamless way. Primitives are used directly and classes are handled by
theirs special foo member function. So it would look something like
this:

int main()
{
int x = 42;
F f;
G g;
foo(x); // Would print 42 to stdout directly
foo(f); // Would print to stdout using f's foo member
foo(g); // Would print to stdout using g's foo member
}

A function template and some added sugar would probably fix this
somehow...
Now, the first problem is that of deciding wether a class has a member
function foo or not. While googling around for a has_foo function I
found this nice post:
http://groups.google.com/group/comp.lang.c++.moderated/msg/410c7680a9b31411
This is a nice trick which uses SFINAE for its purpose. However, it is
too tricky for me to follow...
C++ Templates: The Complete Guide illustrates something similar but
doesn't help.

Slightly modified it reads:

template <typename T>
struct has_foo
{
typedef char One;
typedef struct { char tmp[2]; } Two;

typedef int (T::*PMF)() const;
template <PMF> struct wrapper;

template <typename U>
static One test(U*, wrapper<&U::foo>* = 0); // why these two params?
static Two test(...);

enum { value = (1 == sizeof(has_foo<T>::test((T*)0)))};
};

I do not understand why the static test member function that returns
One must take the two parameters U* and the wrapper-pointer which is
defaulted to 0!? For example, why is it not sufficient to only take one
parameter?:
template <typename U> static One test(wrapper<&U::foo>*);

Anyway, ignoring this allows me write code that works (but...):
int main()
{
F f;
foo(f);
}

where F is a simple class that contains a const foo memfn returning an
int and foo is a function template that delegates the overload
resolution using class template specialization of the class template
Foo_Impl:

template <typename T, bool use_foo = has_foo<T>::value>
struct Foo_Impl
{
static void foo(const T& t) { cout << t.foo() << endl; }
};

template <typename T>
struct Foo_Impl<T,false>
{
static void foo(const T& t) { cout << t << endl; }
};

template <typename T>
void foo(const T& t)
{
Foo_Impl<T>::foo(t);
}

struct F
{
int foo() const { return 42; }
};

However, if I call the function template with an int I get into trouble
- the very trouble I thought was already solved by
Foo_Impl<T,false>::foo(const T&) :-(
Like this:
int main()
{
int x = 42;
foo(x); // FAILS
}

Maybe I have overlooked something trivial, but it would be very
interesting to know why this doesn't work?
What can I do to workaround this?

Thanks in advance!
/Rune


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Carl Barron

unread,
May 2, 2006, 6:43:44 AM5/2/06
to
In article <1146493260....@g10g2000cwb.googlegroups.com>,
<rune...@yahoo.com> wrote:

> struct has_foo
> {
> typedef char One;
> typedef struct { char tmp[2]; } Two;
>
> typedef int (T::*PMF)() const;
> template <PMF> struct wrapper;
>
> template <typename U>
> static One test(U*, wrapper<&U::foo>* = 0); // why these two params?
> static Two test(...);
>
> enum { value = (1 == sizeof(has_foo<T>::test((T*)0)))};
> };
>

so that
struct is_false {int a;};
struct is_true {void foo() {}};

has_foo<is_false> which does not have a member foo selects the
ellipsised function since is_false::foo does not exist and
&is_false::foo is a syntax failure so the only candidate is test(...)
where &is_true::foo is valid syntax and thus test(is_true *,
&is_true::foo) is a better match than test(...) and test(is_true *,
&is_true::foo) is chosen.

Maxim Yegorushkin

unread,
May 2, 2006, 6:52:04 AM5/2/06
to

Because if you do it this way you will have to cast 0 to
wrapper<&T::foo>* in the sizeof expression which will lead to a compile
error for types not having foo member function. But you don't want an
error, rather the first test function overload should be disabled for
classes not having the member function. Since the first test is a
function template substituting U with a type without foo member
function results in the second argument substitution failure, which is
not a compile time error, as it would be if you were to cast 0 to
wrapper<&T::foo>* directly. This is the purpose of the trick to avoid
the error.

Note that has_foo<> metafunction only works for user defined types
(UDT), you can not pass fundamental types (int's, pointers) to it. So
you need to use additional template machinery to meet your goal.

For your task you could exploit facilities provided by boost to avoid
writing boilerplate code yourself. It would look like this:

#include <iostream>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_class.hpp>

template <typename T>
struct has_foo_impl
{
template <int(T::*)()const> struct wrapper;

template <typename U>
static char(&test(U*, wrapper<&U::foo>* = 0))[1];
static char(&test(...))[2];

enum { value = 1 == sizeof test((T*)0) };
};

// this makes has_foo_impl a boost style metafunction predicate
template<class T>
struct has_foo : boost::mpl::bool_<has_foo_impl<T>::value>
{};

// this is for classes with foo member functions
template<class T>
typename boost::enable_if<has_foo<T>, void>::type
foo_impl(T const& t)
{
std::cout << t.foo() << char(0xa);
}

// this is for classes without foo member functions
template<class T>
typename boost::disable_if<has_foo<T>, void>::type
foo_impl(T const& t)
{
std::cout << "a class with no foo() member function" << char(0xa);
}

// this is a dispatcher function for classes only
template<class T>
typename boost::enable_if<boost::is_class<T>, void>::type
foo(T const& t)
{
foo_impl(t);
}

// this is for fundamental types only (not classes)
template<class T>
typename boost::disable_if<boost::is_class<T>, void>::type
foo(T const& t)
{
std::cout << t << char(0xa);
}

struct F
{
int foo() const { return 42; }
};

struct G {};

int main(int ac, char** av)
{
foo(F());
foo(G());
foo(1);
}

P.S. sorry for char(0xa), my backslash key does not work to type
backslash-n.

rune...@yahoo.com

unread,
May 2, 2006, 4:02:43 PM5/2/06
to
>But you don't want an error, rather the first test
>function overload should be disabled for classes not having
>the member function.
This is SFINAE in action which removes the function overload from
the overload resolution set, right?

>Since the first test is a function template
>substituting U with a type without foo member function results
>in the second argument substitution failure, which is not a
>compile time error,

Yes, SFINAE.

>as it would be if you were to cast 0 to wrapper<&T::foo>* directly.

Hmm, I don't really get this last part. I'll give it a thought later
and maybe I'll get it then.

>For your task you could exploit facilities provided by boost to
>avoid writing boilerplate code yourself. It would look like this:

[code snipped]
So elegant and beautiful this is!
And it is not that hard - at least not now when I have the answer ;-)

Thanks for enlighten me Maxim!

/Rune

PS. If someone else thinks this is cool stuff and have a need
to read up on this stuff I can recommend the nice article:
"Function overloading based on arbitrary properties of types"
by Jaakko Järvi, Jeremiah Willcock, Howard Hinnant, and
Andrew Lumsdaine C/C++ Users Journal, 21(6):25--32, June 2003
http://www.ddj.com/184401659

Maxim Yegorushkin

unread,
May 2, 2006, 5:56:56 PM5/2/06
to
rune...@yahoo.com wrote:

[]

> >as it would be if you were to cast 0 to wrapper<&T::foo>* directly.
> Hmm, I don't really get this last part. I'll give it a thought later
> and maybe I'll get it then.

I meant this:

template <typename U>
static char(&test(wrapper<&U::foo>*)[1];
static char(&test(...))[2];

In this case one would have to write the test as follows:

enum { value = 1 == sizeof test((wrapper<&U::foo>*)0) };

The explicit cast to wrapper<&U::foo>* does result in a compilte time
error for types without foo member function. If you don't cast the
second overload is always chosen. I am not sure, but my explanation is
that 0 is an rvalue of integer type [4.10]. A conversion is still
required to make it a r-value of a pointer type and this conversion is
not perfomed implicitly when the compiler considers a function
template. This is why one would need to cast 0 explicitly.

Gene Bushuyev

unread,
May 3, 2006, 5:37:04 AM5/3/06
to
<rune...@yahoo.com> wrote in message
news:1146594892.8...@e56g2000cwe.googlegroups.com...
[...]

>>Since the first test is a function template
>>substituting U with a type without foo member function results
>>in the second argument substitution failure, which is not a
>>compile time error,
> Yes, SFINAE.
>
>>as it would be if you were to cast 0 to wrapper<&T::foo>* directly.
> Hmm, I don't really get this last part. I'll give it a thought later
> and maybe I'll get it then.

The reason is the same why you have to cast 0 to T* in the original example.
Otherwise compiler has no ability to deduce the template argument of test<U>:

enum { value = (1 == sizeof(has_foo<T>::test((T*)0)))};

Of course, writing explicitly test<T>(0) would solve this problem, but also
prevent test(...) from being considered as overload. But then making the second
function a template:

template <typename U>
static Two test(...);

should make the trick work again. So you can implement your idea in this
modified way:

template <typename U>
static One test(wrapper<&U::foo>*);

template <typename U>
static Two test(...);

enum { value = (1 == sizeof(has_foo<T>::test<T>(0)))};

You can have a lot of fun with templates provided you have a good compiler. My
attempt to compile the above succeeded with Comeau, failed in VC8 with "could
not deduce template argument for 'U'", failed in gcc3.3.1 with "internal
compiler error: Segmentation fault," failed in Borland C++ 5.82 with "Cannot
(yet) use member overload resolution during template instantiation ...", failed
in Digital Mars 8.42n with "can't take address of register, bit field, constant
or string."
That bouquet of failures is making me doubt the correctness of that code. Well,
I don't see anything wrong with it. Do you ?

--
Gene Bushuyev (www.gbresearch.com)
----------------------------------------------------------------
There is no greatness where there is no simplicity, goodness and truth. ~ Leo
Tolstoy

0 new messages