简体   繁体   中英

c++ member functions with unresolved overload calling member functions

I'm trying to make an object with a member function that calls another member function like so

foo foo1 = new foo(1, 2);
foo1.print(printj);

I have class:

class foo
{
   public:
     foo(int x, int y) {i = x; j = y;};
     void const print(void const f());
     void const printi();
     void const printj();
   private:
     int i;
     int j;
}

and my implementation is something like:

void const foo::printi(){
   std::cout << i;
}
void const foo::printj(){
   std::cout << j;
}
void const foo::print(void const f()){
   f();
}

I'm getting an error of [Error] no matching function for call to 'foo::print()'

Why is that, and how can i fix it?

You need to:

  1. Declare the pointer-to-member function parameter as such:
    void const print(void const (foo::*f)());
  1. Pass the member function pointer correctly:
    foo1.print(&foo::printj);
  1. Call it with an actual instance (member function call requires an instance):
    void const foo::print(void const (foo::*f)()){
        (this->*f)();
    }

Alternatively you can make the instance an additional parameter or use std::bind or boost::bind to bind them together.

that is not the way how to declare a pointer to a member function you have to declare it this way:

const void (Foo::*ptrFUnc)(void) // or any number of parameters and type 

this example shows how:

#include <iostream>
using namespace std;

class Foo
{
    public:
        void print(const void(Foo::*Bar)()const)const;
        const void FooBar()const;
        const void Baz   ()const;
};

void Foo::print(const void(Foo::*Bar)()const)const
{
    (this->*Bar)();
}

const void Foo::FooBar()const
{
    cout << "FooBar()" << endl;
}

const void Foo::Baz()const
{
    cout << "Baz()" << endl;
}

int main()
{
    Foo theFoo;
    theFoo.print(theFoo.Baz);
    theFoo.print(theFoo.FooBar);

    return 0;
}

Note: This answer is aimed at general-case scenarios and future-proofing, and thus examines the possibility of accepting member functions with different numbers of arguments, and of modifying the function in the future. If this isn't an issue, the easiest solution is to manually specify the pointer-to-member-function, as described in the other answers.

A short summary is at the bottom.


There are also two alternatives to declaring the function's type manually, as shown in the other answers, both involving templates:

  1. Declare it manually.
  2. First alternative: Use templates to specialise the pointer-to-member-function, while explicitly specifying the class.
  3. Second alternative: Use templates to deduce the pointer-to-member-function, with no explicit specifications.

In all three cases (manual declaration, and the two alternatives listed here), the usage syntax is identical:

foo1.print(&foo::printj);

As the other answers show, the syntax for declaring it manually is as follows:

// #1: Explicit declaration.
void const foo::print(void const (foo::* f)()) {
    (this->*f)();
}

I won't go into much detail on this, as they already cover it. However, this option does have the issue that if you want to accept pointers to member functions which take one or more parameters, you need to manually overload it to accomodate this.

void const foo::print(void const (foo::* f)());
void const foo::print(void const (foo::* f)(int), int);
// And so on...

The first alternative looks a bit complex if you're not used to templates, but is relatively simple to implement.

// 2a: Simplest implementation.
template<typename Return, typename... ArgTypes>
void const foo::print(Return (foo::* f)(ArgTypes...), ArgTypes... args) {
    (this->*f)(args...);
}

Or...

// 2b: Works roughly the same way, but gives cleaner, more readable error messages.
template<typename Return, typename... ArgTypes, typename... Args>
void const foo::print(Return (foo::* f)(ArgTypes...), Args... args) {
    (this->*f)(args...);
}

This accepts any pointer-to-member-function which points to a member of foo , regardless of return and parameter types. If the function takes parameters, it also accepts a number of parameters equal to that function's.

Note that the primary difference between the two is that if not passed the correct number of parameters for the function, the first will give an error about not being able to deduce ArgTypes... due to mismatched template parameter packs, while the second will give an error about not having the correct number of parameters to call f() .

[Mechanically, the difference is that the first uses the same template parameter pack in both the pointer and the parameter list, which requires that it be identical in both places (and thus detects the error as a deduction failure when print() is called), while the second uses a separate template parameter pack for each (and thus detects the error as a parameter count mismatch when the pointed-to function, f , is called).]


The second alternative looks cleaner still, and provides cleaner error messages.

template<typename MemberFunction>
void const foo::print(MemberFunction f){
   (this->*f)();
}

This can be easily modified to accept member functions which take parameters, similarly to the first alternative.

// 3: Take pointer-to-member-function and any function parameters as template parameters.
template<typename MemberFunction, typename... Args>
void const foo::print(MemberFunction f, Args... args){
   (this->*f)(args...);
}

It will also give the cleanest error message if passed the wrong number of parameters for the function, because the error occurs when calling f instead of at overload resolution or template deduction. This makes it the easiest to troubleshoot, if necessary.


So, this leaves us with three options, one of which can be done either of two ways:

class foo
{
   public:
     foo(int x, int y) {i = x; j = y; test = 42;};

     // -----

     // #1.
     void const print1(void const (foo::* f)());

     // -----

     // #2.
     template<typename Return, typename... ArgTypes>
     void const print2a(Return (foo::* f)(ArgTypes...), ArgTypes... args);

     template<typename Return, typename... ArgTypes, typename... Args>
     void const print2b(Return (foo::* f)(ArgTypes...), Args... args);

     // -----

     // #3.
     template<typename MemberFunction, typename... Args>
     void const print3(MemberFunction f, Args... args);

     // -----

     void const printi();
     void const printj();

     // For testing.
     void const printParams(int i, bool b, char c, double d);
   private:
     int i;
     int j;
   public:
     int test;
};

void const foo::print1(void const (foo::* f)()) {
    (this->*f)();
}

template<typename Return, typename... ArgTypes>
void const foo::print2a(Return (foo::* f)(ArgTypes...), ArgTypes... args) {
    (this->*f)(args...);
}

template<typename Return, typename... ArgTypes, typename... Args>
void const foo::print2b(Return (foo::* f)(ArgTypes...), Args... args) {
    (this->*f)(args...);
}

template<typename MemberFunction, typename... Args>
void const foo::print3(MemberFunction f, Args... args) {
    (this->*f)(args...);
}

// -----
void const foo::printi(){
   std::cout << i;
}
void const foo::printj(){
   std::cout << j;
}

void const foo::printParams(int i, bool b, char c, double d) {
    std::cout << std::boolalpha;
    std::cout << i << ' ' << b << ' ' << c << ' ' << d << '\n';
    std::cout << std::noboolalpha;
}

// -----

foo foo1(1, 2);

Now, mechanically, all three options will accept a pointer-to-member-function, and work as intended. There are a few key differences, however:

  • The first requires the most work to update and maintain (you have to explicitly overload it), but guarantees that print1() will only take pointers-to-member-functions that you specifically allow; if you want it to only take a void const (foo::*)() , it won't take a void const (foo::*)(int) instead. If passed incorrect arguments, its error messages will be the least useful.
  • The second only takes a pointer-to-member-function for the specified class, but accepts any pointer-to-member-function for that class; this makes it easier to update and maintain. If passed incorrect arguments, its error messages will be somewhat useful, but will usually be about template deduction. Of the two versions, print2b() will give cleaner error messages when passed the wrong number of parameters.
  • The third takes anything, but gives the cleanest, most useful error messages if used incorrectly. This is because the error is generated when you call f , instead of when you call print3() . This is as easy to update and maintain as the second option, and opens the possibility to perform dispatching based on the type of pointer it's passed.

So, to demonstrate the difference in error messages, let's see what happens if...
[Error messages paraphrased from Clang, GCC, and MSVC.]
[Note that MSVC template parameter lists have trouble with variadic templates, and can't properly output parameter packs. However, the function's name still contains the full template parameter list.]

  • If passed a pointer-to-member-function with no parameters: All four work properly.

     foo1.print1(&foo::printj); // Output: 2 foo1.print2a(&foo::printj); // Output: 2 foo1.print2b(&foo::printj); // Output: 2 foo1.print3(&foo::printj); // Output: 2 
  • If passed a pointer-to-member-function that takes parameters, and its parameters: print1() fails.

     foo1.print1(&foo::printParams, 3, true, '&', 8.8); // Error: Too many arguments. foo1.print2a(&foo::printParams, 3, true, '&', 8.8); // Output: 3 true & 8.8 foo1.print2b(&foo::printParams, 3, true, '&', 8.8); // Output: 3 true & 8.8 foo1.print3(&foo::printParams, 3, true, '&', 8.8); // Output: 3 true & 8.8 
  • If passed a pointer-to-member-function that takes parameters, and the wrong number of parameters: All four fail.

     foo1.print1(&foo::printParams, 42); // Error: Too many arguments. foo1.print2a(&foo::printParams, 42); // Error: Can't deduce template parameters, // ArgTypes... could be <int, bool, char, double> or <int>. foo1.print2b(&foo::printParams, 42); // Error: Not enough arguments to call f(). // Note: Clang deduces template parameters as: // <const void, int, bool, char, double, int> // Note: GCC deduces template parameters as: // [with Return = const void; ArgTypes = {int, bool, char, double}; Args = {int}] // Note: MSVC deduces template parameters as: // <const void,int,bool,char,double,int> foo1.print3(&foo::printParams, 42); // Error: Not enough arguments to call f(). // Note: Clang deduces template parameters as: // <const void (foo::*)(int, bool, char, double), int> // Note: GCC deduces template parameters as: // [with MemberFunction = const void (foo::*)(int, bool, char, double); Args = {int}] // Note: MSVC deduces template parameters as: // <const void(__thiscall foo::* )(int,bool,char,double),int> 
  • If passed a regular function pointer: All four fail.

     void const bar() {} foo1.print1(&bar); // Error: Can't convert void const (*)() to void const (foo::*)(). foo1.print2a(&bar); // Error: Can't deduce template parameters, mismatched function pointers. foo1.print2b(&bar); // Error: Can't deduce template parameters, mismatched function pointers. foo1.print3(&bar); // Error: void const (*)() isn't a pointer-to-member, can't be used with "->*". 
  • If passed a pointer-to-member-function for the wrong class: All four fail.

     class oof { public: void const printj() {} }; foo1.print1(&oof::printj); // Error: Can't convert void const (oof::*)() to void const (foo::*)(). foo1.print2a(&oof::printj); // Error: Can't deduce template parameters, mismatched foo* and oof*. foo1.print2b(&oof::printj); // Error: Can't deduce template parameters, mismatched foo* and oof*. foo1.print3(&oof::printj); // Error: Can't use a void const (oof::*)() with a foo*. 
  • If passed a pointer-to-member-data: All four fail.

     foo1.print1(&foo::test); // Error: Can't convert int foo::* to void const (foo::*)(). foo1.print2a(&foo::test); // Error: Can't deduce template parameters, mismatched // int foo::* and Return (foo::*)(ArgTypes...). foo1.print2b(&foo::test); // Error: Can't deduce template parameters, mismatched // int foo::* and Return (foo::*)(ArgTypes...). foo1.print3(&foo::test); // Error: int foo::* can't be used as a function. 
  • If passed a regular pointer: All four fail.

     foo1.print1(&foo); // Error: Can't convert foo* to void const (foo::*)(). foo1.print2a(&foo); // Error: Can't deduce template parameters, mismatched // foo* and Return (foo::*)(ArgTypes...). foo1.print2b(&foo); // Error: Can't deduce template parameters, mismatched // int foo::* and Return (foo::*)(ArgTypes...). foo1.print3(&foo); // Error: foo* isn't a pointer-to-member, can't be used with "->*". 
  • If passed an integral value: All four fail.

     foo1.print1(3); // Error: Can't convert int to void const (foo::*)(). foo1.print2a(3); // Error: Can't deduce template parameters, mismatched // int and Return (foo::*)(ArgTypes...). foo1.print2b(3); // Error: Can't deduce template parameters, mismatched // int and Return (foo::*)(ArgTypes...). foo1.print3(3); // Error: int isn't a pointer-to-member, can't be used with "->*". 

And so on...

Of these options, print3() consistently gives the cleanest error messages when misused, making it the best option when everything else is equal. print2b() gives cleaner error messages when called with the wrong number of parameters, but otherwise matches print2a() .


Summary:

There are three ways to take a pointer-to-member-function, as described above:

  1. Declare it manually, as described in other answers.

     void const foo::print(void const (foo::* f)()); 
  2. Use templates to specialise it, and take any parameters it might need.

     template<typename Return, typename... ArgTypes> void const foo::print(Return (foo::* f)(ArgTypes...), ArgTypes... args); 

    Or...

     template<typename Return, typename... ArgTypes, typename... Args> void const foo::print(Return (foo::* f)(ArgTypes...), Args... args); 
  3. Take the function as a template parameter, and any parameters it may need.

     template<typename MemberFunction, typename... Args> void const foo::print(MemberFunction f, Args... args); 

Of these:

  • The first option gives you the most control over which functions print() is allowed to take, but requires you to explicitly overload it for each type of pointer-to-member-function you want to allow (eg void (foo::*)() or int (foo::*)(int, int) ); this makes it the least future-proof, because you need to update the function if you add new functions to foo and want it to take them.
  • The second and third options, conversely, are future-proof, and will take any pointer-to-member-function. However, they don't allow you to restrict what kind of member function you pass, unless you put extra effort into it.
  • If used incorrectly, the third option will give the cleanest error message, which is useful when troubleshooting. Conversely, the first option will usually emit a conversion error, and the second option will usually emit a "can't deduce template parameters" error. If called with a member function that takes parameters, when not supplied with those parameters, both the third option and the second version of the second option will give the correct error; the third option will emit a cleaner, easier-to-read error, however.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM