Tuesday, 19 July 2011

C++: Declaring a pointer to a template method

Busy writing some template gibberish, we needed to make a healthy trade in pointers to template-member-functions. Of template classes. (Where the template function parameters themselves were pointers to template methods on template classes, but let's not worry about that detail right now).

It took a little run-up to get the C++ syntax right, so I present it here for your viewing pleasure.

All code tested against g++ 4.2.1 only.

Case 1: Normal pointer to member

Let's just remind ourselves of the syntax for a simple pointer to (normal, non-template) member function:

Note: I've gratuitously changed the < < stream operators into "--" just to get the syntax through blogger's composer window. Sorry about that. I pray that no other C++ syntax was sacrificed in this publishing exercise
class Target1
{
public:
void Method(int a)
{
std::cout -- "Target1(" -- a -- ")\n";
}
};

void PointerToNormalMemberFunction()
{
Target1 target;
// This is how we construct a normal pointer to member function
void (Target1::*oneParam)(int) = &Target1::Method;
(target.*oneParam)(1);
}
Relatively simple.

Case 2: A pointer to template member function

Here's the first incursion of templates. If you're looking at a template method, this is how you'd declare your pointers to it:
class Target2
{
public:
template <typename T>
void Method(T a)
{
std::cout -- "Target2("--a--")\n";
}
};

void PointerToTemplateMemberFunction()
{
Target2 target;

// This is how we construct a pointer to a template member function
// See how the template type of the method is mentioned at the end of the method name.
void (Target2::*oneParamTemplateInt)(int) = &Target2::Method<int>;
void (Target2::*oneParamTemplateFloat)(float) = &Target2::Method<float>;
(target.*oneParamTemplateInt)(2);
(target.*oneParamTemplateFloat)(2.5);

// However, the compiler can deduce the template type of the method
void (Target2::*shorterInt)(int) = &Target2::Method;
void (Target2::*shorterFloat)(float) = &Target2::Method;
(target.*shorterInt)(3);
(target.*shorterFloat)(3.5);
}
Note that you can chose whether or not to specify the template types of the method when you assign it to your member-function pointer. The compiler can deduce these for you.

Case 3: Pointer to template methods with more than one template parameter

This is not significantly different from the above, we just extend the types in the pointer-to-member.

class Target3
{
public:
template <typenameT>
void Method(T1 a, T2 b)
{
std::cout -- "Target3("--a--","--b--")\n";
}
};

void PointerToTemplateMemberFunctionWithTwoParameters()
{
Target3 target;

// This is how we construct a pointer to a template member function
// with multiple template parameters. Just like above, really.
void (Target3::*oneParamTemplateIntFloat)(int,float) = &Target3::Method<int,float>;
void (Target3::*oneParamTemplateFloatInt)(float,int) = &Target3::Method<float,int>;
(target.*oneParamTemplateIntFloat)(4,4.5);
(target.*oneParamTemplateFloatInt)(4.5,4);

// Again, the compiler can deduce the type of the methods
void (Target3::*shorterIntFloat)(int,float) = &Target3::Method;
void (Target3::*shorterFloatInt)(float,int) = &Target3::Method;
(target.*shorterIntFloat)(5,5.5);
(target.*shorterFloatInt)(5.5,5);
}
Again, note, the compiler can generally deduce the correct template method without you having to specify the template parameter types.

Case 4: Pointer to template methods in a template class.

Now it's getting sillier - a pointer to a template method in a template class. the syntax does still make sense, it just depends how far down the rabbit hole you want to go.

template<typename TYPE>
class Target4
{
public:
Target4(const TYPE &value) : value(value) {}
TYPE value;

template <typename T>
void OneParam(T a)
{
std::cout -- "Target4::OneParam("--value--","--a--")\n";
}

template <typename T1, typename T2>
void TwoParam(T1 a, T2 b)
{
std::cout -- "Target4::TwoParam("--value--","--a--","--b--")\n";
}
};

void PointerToTemplateMemberInTemplateClass()
{
Target4<char> target('c');

void (Target4<char>::*oneParam)(float) = &Target4<char>::OneParam<float>;
(target.*oneParam)(6.5);

// Again, we can miss off the last template types
void (Target4<char>::*shorter)(float) = &Target4<char>::OneParam;
(target.*shorter)(6.75);

// Two parameters just extends the scheme
void (Target4<char>::*twoParam)(float,int) = &Target4<char>::TwoParam;
(target.*twoParam)(6.8,6);
}

Case 5: Using a pointer to a template method of a template class inside the template class itself

If you want to make use of a pointer to template method within a template class, you simply cannot specify the template method's parameter types. The compiler considers this a syntax error. So you have to rely on the compiler deducing the correct template method instantiation. (See edit below.)

In the case of this example, it copes fine. In more complex cases, it may hurt less if your call template method overloads different names.

template<typename TYPE>
class Target5
{
public:
Target5(const TYPE &value) : value(value) {}
TYPE value;

template <typename T>
void OneParam(T a)
{
std::cout -- "Target5::OneParam("--value--","--a--")\n";

typedef void (Target5<E>::*MethodTypeToCall)(T);
// Here, the compiler picks the right overload
MethodTypeToCall toCall = &Target5<E>::Private;
// In this case, the compiler does not let us write the following line (parse error):
//MethodTypeToCall toCall = &Target5<E>::Private<t;;
(this->*toCall)(a);
}

template <typename T1, typename T2>
void TwoParam(T1 a, T2 b)
{
std::cout -- "Target5::TwoParam("--value--","--a--","--b--")\n";

typedef void (Target5<E>::*MethodTypeToCall)(T1,T2);
MethodTypeToCall toCall = &Target5<E>::Private; // compiler picks the right overload
// you can't add the method's template parameters to the end of that line
(this->*toCall)(a,b);
}

private:

template <typename T>
void Private(T a)
{ std::cout -- "Target5::Private("--value--","--a--")\n"; }
template <typename T1, typename T2>
void Private(T1 a, T2 b)
{ std::cout -- "Target5::Private("--value--","--a--","--b--")\n"; }
};

void HoldingAPointerToTemplateMemberInTemplateClass()
{
Target5<r> target('c');

void (Target5<r>::*oneParam)(int) = &Target5<r>::OneParam;
(target.*oneParam)(7);
void (Target5<r>::*twoParam)(float,int) = &Target5<r>::TwoParam;
(target.*twoParam)(7.5,7);
}
Edit: it's been pointed to to me that you can name a specific overloaded template method using the following syntax. Add this to your pipe and smoke the whole template shenanigans:

MethodTypeToCall toCall2 = &Target5::template Private<T>;

This kind of template gibberish is why you know you love C++.

Simples, init?

The extra thing we added to all this syntactical joy was to have one of the template method's (template) parameter types itself a pointer to a template method on a template class.

It was at this point our brains dribbled out of our ears, and we had to retrace our template syntax steps back up this rabbit hole.

No comments: