Link to home
Start Free TrialLog in
Avatar of phoffric
phoffric

asked on

C++ Compiler Error: Passing a Pointer-To_Member to a template function

Ref: https://www.experts-exchange.com/questions/29138856/Unable-to-Run-Program-Using-a-Pointer-to-a-Template-Method-Inside-a-Template-Class.html

I tried to extend the referenced example by trying to pass a Pointer to Member function into a template. I am doing this because I have another application that has a template function, and I would like to pass a Pointer To Member construct into it. The below program builds and runs, but the pass_PtrToMember_Into_Function function is not invoked. When I modify the call to handle a warning, I get error messages shown below.
#include <iostream>

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

	template <typename T>
	int OneParam(T a)
	{
		std::cout << "\nTarget5::OneParam(" << value << "," << a << ")\n";

		typedef void (Target5<TYPE>::*MethodTypeToCall)(T);
		MethodTypeToCall toCall = &Target5<TYPE>::Private;  // Here, the compiler picks the right overload

		auto toCall2 = &Target5<TYPE>::Private<T>;  // this alternative works OK
		(this->*toCall)(a);
		(this->*toCall2)(a);

		return 1;
	}

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

		auto toCall2 = &Target5<TYPE>::Private<T1, T2>;
		(this->*toCall2)(a, b);

		return 2;
	}

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";
	}
};

template<typename TRG, typename T, T TRG::*m>
void pass_PtrToMember_Into_Function(TRG& tgt, T a)
{
	tgt.*m(a);
}


template <typename r, r val>		//@@ made this a tmplate function
void HoldingAPointerToTemplateMemberInTemplateClass()
{
	Target5<r> target(val);

	int (Target5<r>::*oneParam)(int) = &Target5<r>::OneParam;
	(target.*oneParam)(7);

	// Here is my experiment that gives same result:
	// based on error msgs, I added "template" to below line - what is this for?
	// looking online, it appears that this "template" token may be VS 2017 related??
	auto oneParam2 = &Target5<r>::template OneParam<int>;
	(target.*oneParam)(7);

	int (Target5<r>::*twoParam)(float, int) = &Target5<r>::TwoParam;
	(target.*twoParam)(7.5, 7);
	auto twoParam2 = &Target5<r>::template TwoParam<float, int>;
	(target.*twoParam2)(7.5, 7);

	// 1. I made the 2nd param "int". Originally, it was "void", but that would not compile.
	//    So I changed the OneParam and TwoParam functions to return an "int". How to avoid this?
	// 2. The next line compiles with obvious warning:
	//    warning C4551: function call missing argument list
	//    Not surprisingly, no breakpoint can be made on it.
	pass_PtrToMember_Into_Function<Target5<r>, int, &Target5<r>::template OneParam<int> >;
//	pass_PtrToMember_Into_Function<Target5<r>, int, &Target5<r>::template OneParam<int> >(target, 7);
	std::cout << std::endl;
}

int main()
{
	HoldingAPointerToTemplateMemberInTemplateClass<int, 88>(); // target 5
}

Open in new window

When I change the HoldingAPointerToTemplateMemberInTemplateClass() function as follows, I get a number of error messages:
	pass_PtrToMember_Into_Function<Target5<r>, int, &Target5<r>::template OneParam<int> >(target, 7);
c:\users\ - limited\source\repos\c++template\test_ptr_in_template_function\target5_ee.cpp(81): error C2672: 'pass_PtrToMember_Into_Function': no matching overloaded function found
c:\users\ - limited\source\repos\c++template\test_ptr_in_template_function\target5_ee.cpp(89): note: see reference to function template instantiation 'void HoldingAPointerToTemplateMemberInTemplateClass<int,88>(void)' being compiled
c:\users\ - limited\source\repos\c++template\test_ptr_in_template_function\target5_ee.cpp(81): error C2893: Failed to specialize function template 'void pass_PtrToMember_Into_Function(TRG &,T)'
c:\users\ - limited\source\repos\c++template\test_ptr_in_template_function\target5_ee.cpp(81): note: With the following template arguments:
c:\users\ - limited\source\repos\c++template\test_ptr_in_template_function\target5_ee.cpp(81): note: 'TRG=Target5<r>'
c:\users\ - limited\source\repos\c++template\test_ptr_in_template_function\target5_ee.cpp(81): note: 'T=int'
c:\users\ - limited\source\repos\c++template\test_ptr_in_template_function\target5_ee.cpp(81): note: 'm=Target5<r>::OneParam'

Open in new window

Even when I solved a problem based upon an error message, my solution was a guess.
	// based on error msgs, I added "template" to below line - what is this for?
	// looking online, it appears that this "template" token may be related to a VS 2017 issue??
	auto oneParam2 = &Target5<r>::template OneParam<int>;

Open in new window

SOLUTION
Avatar of evilrix
evilrix
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of phoffric
phoffric

ASKER

Is it possible to get rid of those error messages ?
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Yeah, how about that! :) I added the other function TwoParam just to make sure that I got your pattern. I suppose that if I keep defining new problems for me to solve, and spend a couple days failing at it, then when I see the answer, I will eventually "get it". I think this may not be the right approach. Probably, I need to study C++ templates and metaprogramming.

When I searched online, I keep finding something like this in the template parameters:  R C::*m
And your approach seems so much simpler. Yet I didn't think to try it based on my searches. I don't know how you do it. Any tips? BTW, did you even try using  R C::*m template parameter, or did you just say, that is not necessary?

Is it possible to use this  R C::*m type in my example? If not, when is it always needed?

Thanks again!
I wish I understood how you can solve these template problem in minutes that takes me days. Well, thanks again, Mr. Wizard!
Yes, you can pass the function pointer as a template pointer value, but I've always preferred passing it as an argument, especially as I never work with raw function pointers (I always use std::function). Why would you prefer by template pointer value? I suppose if you want it bound at a function template value rather than functiion parameter to avoid the additional function argument.

The problem is that for each different function (even if the prototype is the same) it'll create a new template instance for each function pointer template value, which will make your binary gets bigger. There is no right way, but passing function pointers as template values will not be extensible when you come to work with C++ generic function objects.

Tips? Read as many books as you can about template programming. At the very least, read this one:

https://www.amazon.com/C-Templates-Complete-Guide-2nd/dp/0321714121

I've been doing meta-template coding for quite a few years bow, so I've already been through most of the pain you're going through. It really is just a matter of practice and a lot of trial and error (that's still true for me at times, even now). Understanding how the compiler handles template instantiation, expansion, resolution, SFINAE, dependent types/names and partial/full specialisation is paramount.

I think you're doing all the right things. All I would probably say is try to work through trivial examples that only tackle one thing you don't understand at a time. If you do get struck on something, try to create a test example that strips it down to the absolute minimum you need to recreate the problem.

For example, in your case you could start trying to pass member function pointers of non-template classes and even hard code the class type, to allow you to focus on just passing the function pointer value.

Remember the KISS principle.

Give that a go and if you get stuck, post back.

Good luck.
Thanks for the tips, Ricky, here and in previous questions. Maybe the pain is worth it. I'll let you know in a couple of years. :)
I have to work on some previous trials related to this thread; and then I have a goal of watching a few Sean Parent videos (about 3-4 hours), and try to understand some of his methodology and performance aspects of modern C++. (This assume that you have nothing bad to say about Sean Parent from Adobe.)
>> you could start trying to pass member function pointers of non-template classes and even hard code the class type, to allow you to focus on just passing the function pointer value.

This was a good exercise. Kept forgetting to add the parens around the ptr to member :(. I need to do actual exercises more often. When I read Stroustrup's book, I just looked at his examples without actually getting on the computer. Worked OK for the easy stuff when OTJ. If I left out an exercise portion, please let me know.
#include <iostream>

class Target
{
public:
   Target(int i, double f, char ch) : a(i), b(f), c(ch) {}
   double add123(int x) { return x + a + b + (c - 'A'); }
   double add12(int x) { return x + a + b ; }
   double add13(int x) { return x + a + (c - 'A'); }

private:
   int a;
   double b;
   char c;
};

void pass_PtrToMember_Into_Function(Target & tgt, int x, double (Target:: * argParam)(int))
{
   std::cout << "argParam(" << x << ") = " << (tgt.*argParam)(x) << std::endl;
}

int main()
{
   Target target = { 1, 2.2,'B' };

   double (Target:: * oneParam)(int) = &Target::add123;
   std::cout << "add123 = " << (target.*oneParam)(100) << std::endl;
   oneParam = &Target::add12;
   std::cout << "add12  = " << (target.*oneParam)(100) << std::endl;
   oneParam = &Target::add13;
   std::cout << "add13 = " << (target.*oneParam)(100) << std::endl;

   
   pass_PtrToMember_Into_Function(target, 200, &Target::add123);
   pass_PtrToMember_Into_Function(target, 300, &Target::add12);
   pass_PtrToMember_Into_Function(target, 400, &Target::add13);
}

Open in new window

Output:
add123 = 104.2
add12  = 103.2
add13 = 102
argParam(200) = 204.2
argParam(300) = 303.2
argParam(400) = 402

Open in new window


>> The problem is that for each different function (even if the prototype is the same) it'll create a new template instance for each function pointer template value, which will make your binary gets bigger.
True. That is a general complaint I hear about templates. But this problem can be mitigated by having a .cpp file instantiating the different templates into one object file, so that at least if two different modules have the same template parameters, there won't be double the code.
Converting the previous post non-template code to templated code was a walk in the park. Good tip to start that way. Don't think I had to do that much in the past when learning other material. But this templating is, well, harder. The walk was over when I stumbled into a ditch by adding line 52 which refers to an additional template variation. Could you please explain the following error - I am hoping that with an explanation, I'll be able to figure out the solution myself.
\ptrtomembers_yestemplate.cpp(33): error C2064: term does not evaluate to a function taking 1 arguments
\ptrtomembers_yestemplate.cpp(52): note: see reference to function template instantiation 'void pass_PtrToMember_Into_Template_Function_1<TGT_IDCI,int,double(__thiscall Target<int,double,char,int>::* )(FNCT_ARG_TYPE)>(TARG &,FNCT_ARG_TYPE,PTMF)' being compiled
1>            with
1>            [
1>                FNCT_ARG_TYPE=int,
1>                TARG=TGT_IDCI,
1>                PTMF=double (__thiscall Target<int,double,char,int>::* )(int)
1>            ]

Open in new window

#include <iostream>

template <typename PR_TYPE1, typename PR_TYPE2, typename PR_TYPE3,
          typename FNCT_ARG_TYPE>
class Target
{
public:
   Target(int i, double f, char ch) : a(i), b(f), c(ch) {}
   double add123(FNCT_ARG_TYPE x) { return x + a + b + (c - 'A'); }
   double add12(FNCT_ARG_TYPE x) { return x + a + b ; }
   double add13(FNCT_ARG_TYPE x) { return x + a + (c - 'A'); }

private:
   PR_TYPE1 a;
   PR_TYPE2 b;
   PR_TYPE3 c;
};

using TGT_IDCI = Target<int, double, char, int>;

void pass_PtrToMember_Into_Function(TGT_IDCI & tgt, int x, double (TGT_IDCI:: * argParam)(int))
{
   std::cout << "argParam(" << x << ") = " << (tgt.*argParam)(x) << std::endl;
}

template <
   typename TARG, // target class
   typename FNCT_ARG_TYPE,   // type of value to non-tmplate Target::addnnn() function
   typename PTMF // pointer to member function type
>
void pass_PtrToMember_Into_Template_Function_1(TARG & tgt, FNCT_ARG_TYPE x, PTMF m)
{
   std::cout << "argParm1(" << x << ") = " << tgt.*m(x) << std::endl;
}

int main()
{
   TGT_IDCI target = { 1, 2.2,'B' };

   double (TGT_IDCI:: * oneParam)(int) = &TGT_IDCI::add123;
   std::cout << "add123 = " << (target.*oneParam)(100) << std::endl;
   oneParam = &TGT_IDCI::add12;
   std::cout << "add12  = " << (target.*oneParam)(100) << std::endl;
   oneParam = &TGT_IDCI::add13;
   std::cout << "add13 = " << (target.*oneParam)(100) << std::endl;

   
   pass_PtrToMember_Into_Function(target, 200, &TGT_IDCI::add123);
   pass_PtrToMember_Into_Function(target, 300, &TGT_IDCI::add12);
   pass_PtrToMember_Into_Function(target, 400, &TGT_IDCI::add13);

   pass_PtrToMember_Into_Template_Function_1(target, 500, &TGT_IDCI::add123);
}

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
As soon as you mentioned "round brackets" and "order of precedence", I knew what problem you were referring to. Thanks!
I got books coming in due time.
#include <iostream>

template <typename PR_TYPE1, typename PR_TYPE2, typename PR_TYPE3,
          typename FNCT_ARG_TYPE>
class Target
{
public:
   Target(int i, double f, char ch) : a(i), b(f), c(ch) {}
   double add123(FNCT_ARG_TYPE x) { return x + a + b + (c - 'A'); }
   double add12(FNCT_ARG_TYPE x) { return x + a + b ; }
   double add13(FNCT_ARG_TYPE x) { return x + a + (c - 'A'); }

private:
   PR_TYPE1 a;
   PR_TYPE2 b;
   PR_TYPE3 c;
};

using TGT_IDCI = Target<int, double, char, int>;

void pass_PtrToMember_Into_Function(TGT_IDCI & tgt, int x, double (TGT_IDCI:: * argParam)(int))
{
   std::cout << "argParam(" << x << ") = " << (tgt.*argParam)(x) << std::endl;
}

template <
   typename TARG, // target class
   typename FNCT_ARG_TYPE,   // type of value to non-tmplate Target::addnnn() function
   typename PTMF // pointer to member function type
>
void pass_PtrToMember_Into_Template_Function_1(TARG & tgt, FNCT_ARG_TYPE x, PTMF m)
{
   std::cout << "argParm1(" << x << ") = " << (tgt.*m)(x) << std::endl;
}

int main()
{
   TGT_IDCI target = { 1, 2.2,'B' };

   double (TGT_IDCI:: * oneParam)(int) = &TGT_IDCI::add123;
   std::cout << "add123 = " << (target.*oneParam)(100) << std::endl;
   oneParam = &TGT_IDCI::add12;
   std::cout << "add12  = " << (target.*oneParam)(100) << std::endl;
   oneParam = &TGT_IDCI::add13;
   std::cout << "add13 = " << (target.*oneParam)(100) << std::endl;

   
   pass_PtrToMember_Into_Function(target, 200, &TGT_IDCI::add123);
   pass_PtrToMember_Into_Function(target, 300, &TGT_IDCI::add12);
   pass_PtrToMember_Into_Function(target, 400, &TGT_IDCI::add13);

   pass_PtrToMember_Into_Template_Function_1(target, 200, &TGT_IDCI::add123);
   pass_PtrToMember_Into_Template_Function_1(target, 300, &TGT_IDCI::add12);
   pass_PtrToMember_Into_Template_Function_1(target, 400, &TGT_IDCI::add13);
}

Open in new window

Since I never worked with Pointers To Members before, it isn't surprising that I didn't ingrain the operator precedence even though the online docs referred to the need for parens. I guess I need to see the basic table to make it stick.
https://en.cppreference.com/w/cpp/language/operator_precedence

() is Precedence 2
.* is Precedence 4
So,
tgt.*m(x)
gets interpreted as:

( tgt.* (m(x)) )

Thanks again for your help. I think I've beaten this question to death unless you can think of another related exercise for me to bash my head against.
Hahaha. I had a feeling you would solve it. :)

I thought you might find this useful. It's a few basic ways of calling a member function indirectly. There are almost certainly more (this isn't supposed to be definitive), but I thought you might find it a useful comparison. In 99% of cases, the bind method (or lambda in place of bind) is probably the simplest and easiest to work with.

I hope this helps.

#include <iostream>
#include <functional>

struct Foo
{
   void func(int i) const
   {
      std::cout << i << std::endl;
   }
};

template<typename C, typename R, typename T, R (C::*m)(T) const>
void caller1(C const & c, int i)
{
   (c.*m)(i);
};

template<typename C, typename M>
void caller2(C const & c, M const m, int i)
{
   (c.*m)(i);
};

template<typename F>
void caller3(F const & f, int t)
{
   f(t);
}

template<typename R, typename T>
void caller4(std::function<R(T)> const & f, int t)
{
   f(t);
}

int main()
{
   using namespace std::placeholders;

   auto const & foo = Foo();
   auto const & bind = std::bind(&Foo::func, std::ref(foo), _1);
   auto const & lamb = [&foo] (int i) { foo.func(i); };

   // working with raw function pointer (yuk!)
   caller1<Foo, void, int, &Foo::func>(foo, 1);
   caller2<Foo>(foo, &Foo::func, 2);

   // working with function objects (functors) makes these calls interchangable
   caller3(bind, 3);
   caller4<void, int>(lamb, 4);

   caller3(lamb, 5);
   caller4<void, int>(bind, 6);
}

Open in new window

I started laughing at myself when I couldn't figure out the last error message. LOL.

Now, it's time to start learning about std::bind and std::function. I am a little familiar with std::bind, but I have seen some code OTJ which I could not decipher - I think there were a dozen _'s as arguments in one function (but not sure- was years ago). I did take a very brief look at std::function a year ago, but failed to see the power of its utility at that time. Had some trouble understanding the documentation. One step at a time, I guess.
Much obliged for your guidance. :)
I started looking at your last code post. Some techniques will have to wait till I do some basic reading. Starting with caller1 (i.e., the "yuk" category), I see on line 12 that its template parameters are:
template<typename C, typename R, typename T, R (C::*m)(T) const>
And I compared this with OP Line 49 which generated the listed errors.
template<typename TRG, typename T, T TRG::*m>
I could not understand those error messages and didn't see a problem with my attempt. But following your pattern, I change the template paramters to:
template<typename TRG, typename T, T (TRG::*m)(T)>
and now the OP program works. Is the trailing (T) absolutely required to represent the Ptr-To-Member-Function's argument(s)? I thought that in my recent travels the past couple of weeks, I saw templates without the argument, and the body of the template was able to handle the arguments - probably mistaken on my part.

What in the OP error messages points me to identify and fix the problem? (I will have to setup again a virtual Linux to look at alternative error messages - I used to do that at work when both were available.)
I'll take a proper look at this later, in the meantime you can avoid setting up Linux by making use of ideone. Also, if you're using Windows 10, did you know you can now run Linux as a native environment?

I'll get back to you on the rest of your post a bit later.
>> Is the trailing (T) absolutely required to represent the Ptr-To-Member-Function's argument(s)?
I think this new template syntax is clouding the basics . We need a signature which includes the functions arguments, so I think the answer is yes - the trailing T is absolutely required.

 I got different sets of error messages from linux, but still have trouble using them to help me understand the problem ( even after knowing the solution from this thread ).
BTW, the linux version takes care of the OP line 52 operator precedents error.

Gcc 6.3 errors:
https://ideone.com/89HlP2

Clang 4.0 errors:
https://ideone.com/G9TJKu
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you for giving it a try. What happens is that the new-for-me Ptr-to-member syntax along with its usage in templates just confused me as I was scrounging around the web looking for snippets. What you say makes sense. I am still not fluent though. Sorry to have thrown you off-track.
No worries.