Reverse traits lookup?
I have been looking for a design that solves the following problem. It will take a few words to describe.
We have four types A1, A2, B, and C.
We would like to write a function fn that takes as parameter a type P. Using traits, P resolves within A1/A2/B/C to PA1/PA2/PB/PC.
The implementation of fn is the same for PA1 and PA2, but it is different for PB and PC, both between them as well as from that of PA1/PA2.
#include <cassert>
struct PA1 {};
struct PA2 {};
struct PB {};
struct PC {};
struct A1 { typedef PA1 P; };
struct A2 { typedef PA2 P; };
struct B { typedef PB P; };
struct C { typedef PC P; };
template<typename T> char fn(typename T::P)
{
return 'a';
}
char fn(开发者_如何转开发B::P) { return 'b'; }
char fn(C::P) { return 'c'; }
int main()
{
PA1 pa1;
PA2 pa2;
PB pb;
PC pc;
assert( fn<A1>(pa1) == 'a' );
assert( fn<A2>(pa2) == 'a' );
assert( fn(pb) == 'b' );
assert( fn(pc) == 'c' );
}
The advantage of the code above is that the implementation of fn for PA1 and PA2 is not repeated.
But here is the snag. The function calls are not symmetric. It is
fn<A1>(pa1)
and
fn<A2>(pa2)
for A1/A2/PA1/PA2, but just fn(pb)
and
fn(pc)
for B/C/PB/PC.
This precludes using fn(..) within yet another (not shown) class template.
Normally this is not an issue. The template parameter can be deduced for parameterized functions. That doesn't work here. We'd be asking the compiler to locate the type among A1/A2/B/C for which P resolves to one of PA1/PA2/PB/PC.
What would you do?
I think that you are confusing two things here:
The implementation of
fn
is the same for bothPA1
andPA2
Does not preclude PA1
and PA2
from being distinct types with distinct typedef
.
Which we can therefore use as is:
template <typename PA>
char fn(PA pa) {
typedef typename PA::type Type; // A1 or A2
return 'a';
}
char fn(PB);
char fn(PC);
If you cannot actually modify P
itself, you can always introduce a traits class.
template <typename T>
struct PTraits;
template <>
struct PTraits<PA1> { typedef A1 type; };
template <>
struct PTraits<PA2> { typedef A2 type; };
// ...
I would note that Herb Sutter recommend not to use function specializations and to prefer overload, because the interaction between overload (esp. with template involved) is specialization is extremely tricky... and it's hard to fathom which function will get called from a given set of parameters.
You can't really have the compiler go in reverse; what happens if two structs have P
as a typedef
for int
?
If you do have a one to one mapping of T
to T::P
, then you will need to tell the compiler how to go from T::P
back to T
explicitly.
To keep things symmetric you could use template specialisation:
template<> char fn<B>(B::P) { return 'b'; }
template<> char fn<C>(C::P) { return 'c'; }
then your calls would all look like:
assert( fn<B>(pb) == 'b' );
assert( fn<C>(pc) == 'c' );
Interestingly, even with explicit template instantiation like:
template char fn<A1>(A1::P);
template char fn<A2>(A2::P);
we don't get inferred types.
精彩评论