meta-programming on functions in template class

M

Marc

Hello,

I have code like:

template<class A,class B> struct Demo {
// here a large body of code

void f(A);
void f(B);
};

If A and B are the same type, this won't work. To remove the second
overload when A==B, you may think of replacing void with
enable_if<!is_same<A,B>::value,void>::type, but that won't work because
A and B are not dependent names. You can artificially make them
dependent, but that doesn't seem right. You can make the second overload
take a type that is B if A!=B and a dummy type otherwise, but that can
cause problems too. You can have 2 specializations of the class, but you
duplicate plenty of code.

How do you usually handle this sort of thing?
 
J

Jonathan Lee

Hello,

I have code like:

template<class A,class B> struct Demo {
// here a large body of code

void f(A);
void f(B);

};

If A and B are the same type, this won't work. To remove the second
overload when A==B, you may think of replacing void with
enable_if<!is_same<A,B>::value,void>::type, but that won't work because
A and B are not dependent names. You can artificially make them
dependent, but that doesn't seem right. You can make the second overload
take a type that is B if A!=B and a dummy type otherwise, but that can
cause problems too. You can have 2 specializations of the class, but you
duplicate plenty of code.

How do you usually handle this sort of thing?

I'd probably rename the functions. Presumably A and B are
conceptually providing different roles wrt Demo. Name the
functions accordingly. If that really isn't an option, you
can do a partial specialization:

#include <iostream>

template<class A, class B>
class Der {
public:
int f(A) { return 1; }
int f(B) { return 2; }
};

template<class A>
class Der<A, A> {
public:
int f(A) { return 9; }
};

int main() {
Der<int, int> a;
std::cout << a.f(3) << std::endl; // "9"
}

--Jonathan
 
M

Marc

Hello,
I'd probably rename the functions. Presumably A and B are
conceptually providing different roles wrt Demo.

Actually they are playing almost exactly the same role, which is why I
am giving f the same name ;-)
Name the functions accordingly. If that really isn't an option, you
can do a partial specialization:

That's one of the options I was mentioning above, but what of the rest
of the class that gets duplicated?
What if I have another function in the class that needs a similar
treatment with a slightly different condition?
 
S

SG

Hello,

I have code like:

template<class A,class B> struct Demo {
// here a large body of code

void f(A);
void f(B);
};

If A and B are the same type, this won't work. To remove the second
overload when A==B, you may think of replacing void with
enable_if<!is_same<A,B>::value,void>::type, but that won't work because
A and B are not dependent names.

They are. But it won't work because neither f is a function template.
They are normal member functions of a templated class.
You can artificially make them
dependent, but that doesn't seem right.

Sorry, what? Do you have the following in mind?

template<class A,class B>
struct Demo
{
template<class AA>
typename enable_if<
is_same<AA,A>::value && !is_same<AA,B>::value
void>::type f(AA);

void f(B);
};
You can make the second overload
take a type that is B if A!=B and a dummy type otherwise, but that can
cause problems too. You can have 2 specializations of the class, but you
duplicate plenty of code.

How do you usually handle this sort of thing?

By avoiding it. Maybe it's also possible to avoid it in your case. But
that depends on what the code is actually supposed to do. Maybe
someone will come up with an elegant alternative if you describe what
the code is supposed to do.

Cheers!
SG
 
J

Juha Nieminen

Marc said:
You can have 2 specializations of the class, but you
duplicate plenty of code.

That's what inheritance is for. You put the duplicated parts of
each specialization into a base class, and then inherit the
specializations from it to get the shared code. (Design and name
this base class properly, and it will even become good object
oriented design.)
 
R

Richard

[Please do not mail me a copy of your followup]

Juha Nieminen <[email protected]> spake the secret code
That's what inheritance is for.

Actually, no. Its often thought that "inheritance = reuse", and
although I do that, its not what inheritance is for. Inheritance is
to specify an "IS-A" relationship between two entities. There are
many, many ways to achieve reuse and eliminate duplication, and
although inheritance is one way that can be done, it isn't what
inheritance is "for".
 
J

Johannes Schaub (litb)

Marc said:
Hello,

I have code like:

template<class A,class B> struct Demo {
// here a large body of code

void f(A);
void f(B);
};

If A and B are the same type, this won't work. To remove the second
overload when A==B, you may think of replacing void with
enable_if<!is_same<A,B>::value,void>::type, but that won't work because
A and B are not dependent names. You can artificially make them
dependent, but that doesn't seem right. You can make the second overload
take a type that is B if A!=B and a dummy type otherwise, but that can
cause problems too. You can have 2 specializations of the class, but you
duplicate plenty of code.

How do you usually handle this sort of thing?

I try to avoid it. If it comes up anyway, i would probably make them
distinct artificially

template<int> struct i { };
template<typename A, typename B> struct Demo {
void f(A, i<1> = i<2>());
void f(B, i<2> = i<2>());
};
 
J

Johannes Schaub (litb)

Marc said:
Hello,

I have code like:

template<class A,class B> struct Demo {
// here a large body of code

void f(A);
void f(B);
};

If A and B are the same type, this won't work. To remove the second
overload when A==B, you may think of replacing void with
enable_if<!is_same<A,B>::value,void>::type, but that won't work because
A and B are not dependent names. You can artificially make them
dependent, but that doesn't seem right. You can make the second overload
take a type that is B if A!=B and a dummy type otherwise, but that can
cause problems too. You can have 2 specializations of the class, but you
duplicate plenty of code.

How do you usually handle this sort of thing?

I try to avoid it. If it comes up anyway, i would probably make them
distinct artificially

template<int> struct i { };
template<typename A, typename B> struct Demo {
void f(A, i<1> = i<1>());
void f(B, i<2> = i<2>());
};
 
J

Johannes Schaub (litb)

Johannes said:
I try to avoid it. If it comes up anyway, i would probably make them
distinct artificially

template<int> struct i { };
template<typename A, typename B> struct Demo {
void f(A, i<1> = i<1>());
void f(B, i<2> = i<2>());
};

Note that this fails when you call "f" because it will cause an ambiguity.
But depending on your code, that might be the failt of the Demo<T ,T> user
or the fault of Demo. What is your contract?
 
J

Juha Nieminen

Richard said:
Juha Nieminen <[email protected]> spake the secret code


Actually, no. Its often thought that "inheritance = reuse", and
although I do that, its not what inheritance is for. Inheritance is
to specify an "IS-A" relationship between two entities. There are
many, many ways to achieve reuse and eliminate duplication, and
although inheritance is one way that can be done, it isn't what
inheritance is "for".

The subject is not necessarily as black and white as you pose it.

In pure object-oriented design (which is not the same thing as
object-oriented programming, although the latter is usually the practical
implementation of the former) what you say is true: We have concepts,
more abstract ones, and more concrete ones, and each inherited class
should be a more concrete (or specialized) version of the base class
(which is more abstract as a concept).

However, when we are talking about object-oriented programming (which,
as said, can usually be thought as a practical implementation of the
object-oriented design), there can be (and is) more than school of
thought: For example, one states that inheritance should be restricted
to the conceptual abstract-concrete "is-a" relationship, and that any
other "abuse" of inheritance is bad programming. Another school of thought
states that inheritance can be used for code reuse, for grouping code
which is common to more than one class into a single base class, so that
it doesn't have to be duplicated (code repetition is, after all, one of
the biggest sins in programming).

These two concepts don't need to be mutually exclusive. In fact, if you
start grouping common functionality into one single base class, if you do
it properly, you will usually find out that this base class can actually
be designed so that it conforms to a pretty good "is-a" relationship with
the derived classes. Even if the "is-a" relationship is not used directly
per se (ie. no code takes a reference/pointer of the base class type), at
a conceptual level it can still work.

If the common functionality consists of public member functions, I can't
think of a better way than inheritance to do this. In fact, I'd still say
that this is exactly what inheritance is for: Grouping public interface
common to more than one class into a single base class. Since the derived
classes automatically inherit this functionality, they *are* of the same
conceptual type as the base class.
 
J

James Kanze

The subject is not necessarily as black and white as you pose it.
In pure object-oriented design[...]

Just a question: who cares? What you (Juha) are proposing is
good design. Who cares whether someone cares to call it "pure
object-oriented design"? (FWIW: in Smalltalk, the original "OO"
language, the only reason you inherit is for reuse---inhertance
of implementation. Of course, today, use of the term OO has
evolved, and every author means something slightly different by
it.)
 
M

Marc

SG said:
They are. But it won't work because neither f is a function template.
They are normal member functions of a templated class.

Well, more precisely neither A nor B is a template argument of f. Making
f a template function is not enough to allow sfinae on A or B.
Sorry, what? Do you have the following in mind?

template<class A,class B>
struct Demo
{
template<class AA>
typename enable_if<
is_same<AA,A>::value && !is_same<AA,B>::value
void>::type f(AA);

void f(B);
};

For instance, yes. Since in many cases f already takes other template
arguments, I can also use:

template<class T>
typename enable_if<!is_same<typename Second<T,A>::type,B>::value>::type
f(A,T);

where Second just typedefs its second parameter as type. But you can see
why both tricks seem unsatisfactory.
By avoiding it. Maybe it's also possible to avoid it in your case. But
that depends on what the code is actually supposed to do. Maybe
someone will come up with an elegant alternative if you describe what
the code is supposed to do.

Oh, I can work around it in all cases I believe, but it is inconvenient.
I sometimes use derivation, as advised by another poster, but when the
function and the rest of the class are interdependent it is not so nice.
I also sometimes move the function (or class) out of the class, when the
dependencies allow. But most of the time I use ugly tricks as above.

I'll come back here and post an example if I find one that is
particularly hard to work around. Thanks to all for the suggestions.
 
R

Ruslan Mullakhmetov

Note that this fails when you call "f" because it will cause an ambiguity.
But depending on your code, that might be the failt of the Demo<T ,T> user
or the fault of Demo. What is your contract?

You code give me a clue how to handle it. I remembered about
Alexandrescu idea about static dispatching and after couple of hours
playing with templates i finally got following code.

it seems to be working. of course code may be reduced, but i want to sleep.

#include <cstdio>
#include <boost/type_traits/is_same.hpp>

typedef int A;
typedef double B;

template<class A,class B>
struct Demo {
// here a large body of code

enum {
is_same_types = boost::is_same<A,B>::value,
};

template<typename T>
void f(T t) { f<T, is_same_types>(t); }

template <typename T, bool B>
void f(T t){ f2a(t); }

template <> void f<B, false>(B b){ f2b(b); }

void f2a(A){printf( "A\n" );}
void f2b(B){printf( "B\n" );}

};


int main()
{
typedef Demo<A,B> DAB;
typedef Demo<A,A> DAA;
typedef Demo<B,B> DBB;

DAB dab;
printf( "dab.f( A() ); " );
dab.f( A() );
printf( "dab.f( B() ); " );
dab.f( B() );
DAA daa;
printf( "daa.f( A() ); " );
daa.f( A() );
}
 

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
474,432
Messages
2,571,680
Members
48,796
Latest member
Greg L.

Latest Threads

Top