template specialization problem

I

Imre

Hi

I'd like to know what the problem is with the following code. I've
tried compiling it with two compilers (VC++ 8.0, and Comeau online
compiler), and both failed to compile it, saying that there's no B::G()
function. It seems that the primary version of the CallFHelper template
class is selected for both A and B, though I think that the
specialization should be selected for B (after all,
IntToVoid<sizeof(Dummy(&T::F))>::type _is_ void, if T == B; I checked
it with boost's STATIC_ASSERT and is_same).
So, could anyone explain me why the specialization isn't used for B?
Thanks,

Imre

Here's the code:

struct A
{
int G() { return 42; }
};
struct B
{
int F() { return 42; }
};

template <int I>
struct IntToVoid
{
typedef void type;
};

template <class T, typename Enable = void>
struct CallFHelper
{
static void F(T* t)
{
// This is here only to detect if the primary template is used
instead of the spec.
t->G();
}
};

template <typename T>
int Dummy(T);

template <class T>
struct CallFHelper<T, typename IntToVoid<sizeof(Dummy(&T::F))>::type>
{
static void F(T* t)
{
t->F();
}
};

// Call T::F() if it exists, otherwise do nothing
template <class T>
void CallF(T* t)
{
CallFHelper<T>::F(t);
}

int main(int argc, char* argv[])
{
A a;
CallF(&a);

B b;
CallF(&b);

return 0;
}
 
P

Pierre Barbier de Reuille

Imre said:
Hi

I'd like to know what the problem is with the following code. I've
tried compiling it with two compilers (VC++ 8.0, and Comeau online
compiler), and both failed to compile it, saying that there's no B::G()
function. It seems that the primary version of the CallFHelper template
class is selected for both A and B, though I think that the
specialization should be selected for B (after all,
IntToVoid<sizeof(Dummy(&T::F))>::type _is_ void, if T == B; I checked
it with boost's STATIC_ASSERT and is_same).
So, could anyone explain me why the specialization isn't used for B?
Thanks,

Imre

Here's the code:

[...]

template <class T, typename Enable = void>
struct CallFHelper
{
static void F(T* t)
{
// This is here only to detect if the primary template is used
instead of the spec.
t->G();
}
};

template <typename T>
int Dummy(T);

template <class T>
struct CallFHelper<T, typename IntToVoid<sizeof(Dummy(&T::F))>::type>
{
static void F(T* t)
{
t->F();
}
};

// Call T::F() if it exists, otherwise do nothing
template <class T>
void CallF(T* t)
{
CallFHelper<T>::F(t);
}

int main(int argc, char* argv[])
{
A a;
CallF(&a);

B b;
CallF(&b);

return 0;
}

The problem is that your template declaration is ambiguous! How can you
choose between the two CallFHelper given they can both be used with only
one argument and are as specialised in that case ? BTW, g++ does what
*you* want, i.e. calling the second template. If you want it to work,
I'm pretty sure that you just need to change the main template
definition with:

template <class T, typename Enable>
struct CallFHelper
{
static void F(T* t)
{
// This is here only to detect if the primary template is used instead
of the spec.
t->G();
}
};

Now, if you put only one parameter to your template, there is no
ambiguity anymore.

Pierre
 
I

Imre

Pierre said:
The problem is that your template declaration is ambiguous! How can you
choose between the two CallFHelper given they can both be used with only
one argument and are as specialised in that case ?

The second version of CallFHelper is more specialized. The compiler
should choose it whenever "typename
IntToVoid<sizeof(Dummy(&T::F))>::type" is a valid expression (that is,
T::F exists), and its result is void (or the second template param, if
explicitly provided instead of using the default). Otherwise, it should
fall back to the primary template.
Actually this is an old trick that I've seen at many places, and even
I've used it in the past.

To demonstrate, try the following:
- place a typedef void HasF; line in class B
- replace the head of the second CallFHelper version (the
specialization) with this:
template <class T>
struct CallFHelper<T, typename T::HasF>

And it works like a charm. (But it's ugly and redundant.)

But doesn't work with typename IntToVoid<sizeof(Dummy(&T::F))>::type
instead of T::HasF, even though these two should be the same for all
possible Ts (either void or invalid). That's what I don't understand.

Imre
 

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,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top