Template function specialization/overloading

G

gao_bolin

I am facing the following scenario: I have a class 'A', that implements
some concept C -- but we know this, not because A inherits from a
virtual class 'C', but only because a trait tell us so:

class A {};

template <typename T>
struct Is_C
{
static const bool value = false;
};

template <>
struct Is_C<A>
{
static const bool value = true;
};


What I would like to do is, given an instance 'a' of A, call a function
'foo(a)' that would have specific code for class A -- and have default
code for other classes.

The following

template < typename T >
void foo(T t)
{
if (Is_C<T>::value)
{
/* ... */
}
else
{
/* ... */
}
}

would work, but then, it is not extensible to another trait 'Is_D'
unless I modify the function foo. Ideally I would have a function foo
for the general case (corresponding to the 'else') and as many
functions foo for all the different tests, but I am not sure how to do
that. I would like to use the boost::enable_if, but if I use function
overloading, I need to have a disable_if in my 'general case' foo for
each 'specialized' foo:

// General case
template < typename T >
typename disable_if<Is_C<T> >::type // This has to be modified if
another foo is added
foo(T t)
{
/* ... */
}

// Specialization for concept C
template < typename T >
typename enable_if<Is_C<T> >::type
foo(T t)
{
/* ... */
}

I would really appreciate any suggestion for this problem.

Thanks

B.
 
V

Victor Bazarov

I am facing the following scenario: I have a class 'A', that implements
some concept C -- but we know this, not because A inherits from a
virtual class 'C', but only because a trait tell us so:

class A {};

template <typename T>
struct Is_C
{
static const bool value = false;
};

template <>
struct Is_C<A>
{
static const bool value = true;
};


What I would like to do is, given an instance 'a' of A, call a function
'foo(a)' that would have specific code for class A -- and have default
code for other classes.

The following

template < typename T >
void foo(T t)
{
if (Is_C<T>::value)
{
/* ... */
}
else
{
/* ... */
}
}

would work, but then, it is not extensible to another trait 'Is_D'
unless I modify the function foo. Ideally I would have a function foo
for the general case (corresponding to the 'else') and as many
functions foo for all the different tests, but I am not sure how to do
that. I would like to use the boost::enable_if, but if I use function
overloading, I need to have a disable_if in my 'general case' foo for
each 'specialized' foo:

// General case
template < typename T >
typename disable_if<Is_C<T> >::type // This has to be modified if
another foo is added
foo(T t)
{
/* ... */
}

// Specialization for concept C
template < typename T >
typename enable_if<Is_C<T> >::type
foo(T t)
{
/* ... */
}

I would really appreciate any suggestion for this problem.

Interesting problem. Of course, the first think I want to say is that
you're confusing yourself and others calling your "traits" 'Is_C' or
'Is_D'. Apparently your class A _isn't_ C or D, otherwise inheritance
should work just fine. You should name your concept as it is, something
like 'implements_C' or 'implements_D'.

Anyway... I thought of providing the helper for your 'foo':
------------------------------------------------------------------------
#include <iostream>

template<class T> struct implements_C { enum { value = 0 }; };

template<class T, bool b> struct foo_helper {
static void foo(T t)
{
std::cout << "Generic foo\n";
}
};

template<class T> struct foo_helper<T, true> {
static void foo(T t)
{
std::cout << "'true'-specific foo\n";
}
};

template<class T> void foo(T t)
{
foo_helper<T, implements_C<T>::value>::foo(t);
}

class A {};
class B {};

template<> struct implements_C<A> { enum { value = 1 }; };

int main()
{
A a;
foo(a);
B b;
foo(b);
}
------------------------------------------------------------------------

Now, if you need to add another "policy", the 'foo_helper', which does
the actual work, shouldn't change. What you change is the 'foo':
template<class T> void foo(T t)
{
foo_helper<T,
implements_C said:
::foo(t);
}

Here is amended code with two policies:
------------------------------------------------------------------------
#include <iostream>

template<class T> struct implements_C { enum { value = 0 }; };
template<class T> struct implements_D { enum { value = 0 }; };

template<class T, bool b> struct foo_helper {
static void foo(T t)
{
std::cout << "Generic foo\n";
}
};

template<class T> struct foo_helper<T, true> {
static void foo(T t)
{
std::cout << "'true'-specific foo\n";
}
};

template<class T> void foo(T t)
{
foo_helper<T, implements_C<T>::value || implements_D<T>::value >::foo(t);
}

class A {};
class B {};
class AB {};
class C {};

template<> struct implements_C<A> { enum { value = 1 }; };
template<> struct implements_D<B> { enum { value = 1 }; };
template<> struct implements_C<AB> { enum { value = 1 }; };
template<> struct implements_D<AB> { enum { value = 1 }; };

int main()
{
A a;
foo(a);
B b;
foo(b);
AB ab;
foo(ab);
C c;
foo(c);
}
 
G

gao_bolin

Thanks for your answer. However, my problem is slightly different --
let me explain further. I want to leave the possibility open to add a
third function foo in case somebody comes up with another concept D for
which foo should be implemented differently. E.g:

// General case
template < typename T >
// the following line has been modify when third foo was added
typename disable_if<implements_C<T> || implements_D<T> >::type
foo(T t)
{
/* ... */
}

// Specialization for concept C
template < typename T >
typename enable_if<implements_C<T> >::type
foo(T t)
{
/* ... */
}

// Specialization for concept D
template < typename T >
typename enable_if<implements_D<T> >::type
foo(T t)
{
/* ... */
}


One could generalize your idea of helper functions by replacing a bool
by an integer, but I don't like the idea of having to modify the helper
function -- in that sense, I am not sure what benefits come from you
approach compared to the previous collection of functions. Ideally
somebody should be able to add a 'foo' without having to modify the
remaining of the code.
 
S

Shezan Baig

Thanks for your answer. However, my problem is slightly different --
let me explain further. I want to leave the possibility open to add a
third function foo in case somebody comes up with another concept D for
which foo should be implemented differently.


What you need could be achieved by adding a 'tag' inside your traits
class. For example, here's some pseudocode:

struct ATag { };
struct BTag { };
struct CTag { };

template <typename TYPE>
struct Traits;

class SomeClassThatHasATrait { ... };
class SomeClassThatHasBTrait { ... };
class SomeClassThatHasCTrait { ... };

template <>
struct Traits<SomeClassThatHasATrait> { typedef ATag Tag; };

template <>
struct Traits<SomeClassThatHasBTrait> { typedef BTag Tag; };

template <>
struct Traits<SomeClassThatHasCTrait> { typedef CTag Tag; };

template <typename TYPE>
void foo(TYPE object, ATag)
{
... code for types that have A trait ....
}

template <typename TYPE>
void foo(TYPE object, BTag)
{
... code for types that have B trait ....
}

template <typename TYPE>
void foo(TYPE object, CTag)
{
... code for types that have C trait ....
}

template <typename TYPE>
void foo(TYPE object)
{
typedef typename Traits<TYPE>::Tag Tag;
foo(object, Tag());
}

-----------------------------------------

Now, when you want to add a new 'D' trait, all you have to do is define
a tag for it:

struct DTag { };

Define some class that has the D trait:

class SomeClassThatHasDTrait { ... };

template <>
struct Traits<SomeClassThatHasDTrait> { typedef DTag Tag; };

Next, define a foo function for classes that have the D trait:

template <typename TYPE>
void foo(TYPE object, DTag)
{
... code for types that have C trait ....
}


Hope this helps,
-shez-
 
V

Victor Bazarov

Shezan said:
What you need could be achieved by adding a 'tag' inside your traits
class. For example, here's some pseudocode:

struct ATag { };
struct BTag { };
struct CTag { };

template <typename TYPE>
struct Traits;

This has to be somehow defined for "any other type TYPE".
class SomeClassThatHasATrait { ... };
class SomeClassThatHasBTrait { ... };
class SomeClassThatHasCTrait { ... };
[...]

template <typename TYPE>
void foo(TYPE object)
{
typedef typename Traits<TYPE>::Tag Tag;

And it's not going to compile if there is no 'Tag' defined. You'll
need to have some kind of "default traits" and the corresponding
"default" behaviour.
foo(object, Tag());
}

V
 
S

Shezan Baig

Victor said:
Shezan said:
What you need could be achieved by adding a 'tag' inside your traits
class. For example, here's some pseudocode:

struct ATag { };
struct BTag { };
struct CTag { };

template <typename TYPE>
struct Traits;

This has to be somehow defined for "any other type TYPE".
class SomeClassThatHasATrait { ... };
class SomeClassThatHasBTrait { ... };
class SomeClassThatHasCTrait { ... };
[...]

template <typename TYPE>
void foo(TYPE object)
{
typedef typename Traits<TYPE>::Tag Tag;

And it's not going to compile if there is no 'Tag' defined. You'll
need to have some kind of "default traits" and the corresponding
"default" behaviour.



Yeap. If you need default behaviour, then instead of:

template <typename TYPE>
struct Traits;

you need to do:

template <typename TYPE>
struct Traits {
typedef DefaultTag Tag;
};

and create a corresponding foo(TYPE, DefaultTag) function.

-shez-
 
G

gao_bolin

This is indeed in the spirit of what I was looking for. It is still one
footstep of being exactly what I need though, because it doesn't reuse
the information that was already there in the implements_X classes. In
the first place I thought that this problem is hard precisely because
the information is not encoded in the form of class types -- and your
solution is exactly going manually from one form to the other. Is there
a way to go without having to rewrite all these traits specialization?

B.
 
S

Shezan Baig

This is indeed in the spirit of what I was looking for. It is still one
footstep of being exactly what I need though, because it doesn't reuse
the information that was already there in the implements_X classes. In
the first place I thought that this problem is hard precisely because
the information is not encoded in the form of class types -- and your
solution is exactly going manually from one form to the other. Is there
a way to go without having to rewrite all these traits specialization?

B.


Specialize the trait. Its hardly much typing! And even less if you
just copy&paste :)

-shez-
 
G

gao_bolin

That's not that simple. Suppose 'foo' now depends on three parameters,
and I want to specialized foo when all three parameters have a true
std::numeric_limits<>::is_specialized. There is no copy-pasting
possible. And can you just imagine the number of specialized traits I
would have to type?
 
S

Shezan Baig

That's not that simple. Suppose 'foo' now depends on three parameters,
and I want to specialized foo when all three parameters have a true
std::numeric_limits<>::is_specialized. There is no copy-pasting
possible. And can you just imagine the number of specialized traits I
would have to type?


All you need is one tag per overloaded 'foo'. And one specialization
per 'TYPE' that 'foo' can operate on.
 
S

Shezan Baig

I am not sure what you mean. For the example I was mentioning, the
enable_if solution would be

// general case
template <typename T1, typename T2, typename T3 >
typename disable_if_c<
numeric_traits<T1>::is_specialized &&

foo(const T1 &x1, const T2 &x2, const T3 &x3)
{
/*...*/
}

// case when all types have numeric traits
template <typename T1, typename T2, typename T3 >
typename enable_if_c<
numeric_traits<T1>::is_specialized &&

foo(const T1 &x1, const T2 &x2, const T3 &x3)
{
/*...*/
}

What would be the equivalent with your technique? I can see that I
would need only one tag, for the case when all types have numeric
traits, but how would I avoid having a trait with three template
arguments, and having to specialize this trait for all 3-uples of types
with known numeric traits (and there are a lot of them) ? How could I
reuse numeric traits?

Bolin



Create a function like this:


template <typename T1, typename T2, typename T3>
void foo(const T1& x1, IsSpecializedTag,
const T2& x2, IsSpecializedTag,
const T3& x3, IsSpecializedTag)
{
/* ... */
}

template <typename T1, typename T2, typename T3>
void foo(const T1& x1, const T2& x2, const T3& x3)
{
typedef typename Trait<T1>::Tag Tag1;
typedef typename Trait<T2>::Tag Tag2;
typedef typename Trait<T3>::Tag Tag3;

return foo(x1, Tag1(),
x2, Tag2(),
x3, Tag3());
}


Of course, you also have the flexibility to mix and match tags like
this:


template <typename T1, typename T2, typename T3>
void foo(const T1& x1, IsSpecializedTag,
const T2& x2, IsSomethingElseTag,
const T3& x3, IsYetSomethingElseTag)
{
/* ... */
}


and 'foo' will "just work" as long as you have assigned an appropriate
tag for each of the possible types (via specialization).

Hope this helps,
-shez-
 

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

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top