9

I am trying to create a pointer to a member function which has default arguments. When I call through this function pointer, I do not want to specify an argument for the defaulted argument. This is disallowed according to the standard, but I have never before found anything that the standard disallowed that I could not do in some other conformant way. So far, I have not found a way to do this.

Here is code illustrating the problem I'm trying to solve:

class MyObj
{
public:
 int foo(const char* val) { return 1; }
 int bar(int val = 42) { return 2; }
};

int main()
{
 MyObj o;

 typedef int(MyObj::*fooptr)(const char*);
 fooptr fp = &MyObj::foo;
 int r1 = (o.*fp)("Hello, foo.");

 typedef int(MyObj::*barptr)(int);
 barptr bp1 = &MyObj::bar;
 int r2 = (o.*bp1)(); // <-- ERROR: too few arguments for call

 typedef int (MyObj::*barptr2)();
 barptr2 bp2 = &MyObj::bar; // <-- ERROR: Can't convert from int(MyObj::*)(int) to int(MyObj::*)(void)
 int r3 = (o.*bp2)();
    return 0;
}

Any ideas on how to do this in conformant C++ if I do not want to specify any values for the defaulted arguments?

EDIT: To clarify the restrictions a bit. I do not want to specify any default arguments either in the call or in any typedef. For example, I do not want to do this:

typedef int(MyObj::*barptr)(int = 5);

...nor do I want to do this:

typedef int(MyObj::*barptr)(int);
...
(o.barptr)(5);
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • Just call o.foo( "blah" ) and o.bar()? – Goz Feb 08 '10 at 22:26
  • @Goz: Please read the post again. I'm trying to use a function pointer. – John Dibling Feb 08 '10 at 22:30
  • 1
    The *reason* default arguments work is the compiler can see it. With just function pointers, any information like defaults is gone completely. You have to specify it, or make a wrapper. – GManNickG Feb 08 '10 at 22:33
  • @GMan: Right, that's exactly the problem I'm having. I thought there might have been a novel solution I hadn't thought of since, as I said, I've never found anything I couldn't do in standard C++. – John Dibling Feb 08 '10 at 22:41
  • @John: Nope, you simply need to specify the arguments. Like Andrey said, function pointers have zero relation to the function they bind to. – GManNickG Feb 08 '10 at 22:46

4 Answers4

20

It would be rather strange to expect the function pointers to work the way you expect them to work in your example. "Default argument" is a purely compile-time concept, it is a form of syntactic sugar. Despite the fact that default arguments are specified in the function declaration or definition, they really have nothing to do with the function itself. In reality default arguments are substituted at the point of the call, i.e. they are handled in the context of the caller. From the function's point of view there's no difference between an explicit argument supplied by the user or a default one implicitly supplied by the compiler.

Function pointers, on the other hand, are run-time entities. They are initialized at run time. At run-time default arguments simply don't exist. There's no such concept as "run-time default arguments" in C++.

Some compilers will allow you to specify default arguments in function pointer declaration, as in

void foo(int);

int main() {
   void (*pfoo)(int = 42) = foo;
   pfoo(); // same as 'pfoo(42)'
}

but this is not standard C++ and this does not appear to be what you are looking for, since you want the "default argument " value to change at run time depending on the function the pointer is pointing to.

As long as you want to stick with genuine function pointers (as opposed to function objects, aka functors) the immediate workaround would be for you to provide a parameter-less version of your function under a different name, as in

class MyObj 
{ 
public: 
  ...
  int bar(int val = 42) { return 2; } 
  int bar_default() { return bar(); }
}; 

int main() 
{ 
  MyObj o; 

  typedef int (MyObj::*barptr2)(); 
  barptr2 bp2 = &MyObj::bar_default;
  int r3 = (o.*bp2)(); 
  return 0; 
} 

This is, of course, far from elegant.

One can actually argue that what I did above with bar_default could have been implicitly done by the compiler, as a language feature. E.g. given the class definition

class MyObj 
{ 
public: 
  ...
  int bar(int val = 42) { return 2; } 
  ...
}; 

one might expect the compiler to allow the following

int main() 
{ 
  MyObj o; 

  typedef int (MyObj::*barptr2)(); 
  barptr2 bp2 = &MyObj::bar;
  int r3 = (o.*bp2)(); 
  return 0; 
} 

where the pointer initialization would actually force the compiler to implicitly generate an "adapter" function for MyObj::bar (same as bar_default in my previous example), and set bp2 to point to that adaptor instead. However, there's no such feature in C++ language at this time. And to introduce something like that would require more effort than it might seem at the first sight.

Also note that in the last two examples the pointer type is int (MyObj::*)(), which is different from int (MyObj::*)(int). This is actually a question to you (since you tried both in your example): how would you want it to work? With an int (MyObj::*)() pointer? Or with a int (MyObj::*)(int) pointer?

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Re: "Default argument" is a purely compile-time concept -- true, but I was hoping that since the compiler knows the default values in the same translation unit where I call through the pointer, there would be some way to get it to cough up the info. – John Dibling Feb 08 '10 at 22:43
  • @John: And how can the compiler know what the function pointer will point to? It's not bound until run-time; and C++ compilers don't have to do static-analysis. – GManNickG Feb 08 '10 at 22:45
  • @John: I'd say that in any case when the use of function pointers is actually justified, this would be prohibitively expensive. The only reasonable way to implement this would be to stuff all this information into the pointer itself, i.e. use a "fat" pointer. This just isn't done that way in C++. If you need a "fat" pointer, use functor instead. – AnT stands with Russia Feb 08 '10 at 22:48
  • @AndreyT: I suspect my estimation of when the use of function pointers is justified is looser than yours. :) – John Dibling Feb 08 '10 at 22:54
  • @John Dibling: In your case it is "easy" for the compiler to figure out what the arguments are supposed to be only because the pointer bindings are obvious to the compiler. But normally, when the binding are so obvious, there's no need for pointers at all (as in your artificial example). Once the bindings become truly run-time, it simply becomes impossible for the compiler to correctly determine the default argument values (unless it stuffs these values into the pointer itself or uses the approach I described at the end of my answer). – AnT stands with Russia Feb 08 '10 at 22:59
4

You could create functors instead of function pointers of course.

struct MyFunctor {
    int operator() {
        return myobj.bar();
    }

    MyFunctor(MyObj &obj) : myobj(obj) {}
    MyObj &myobj;
};

then:

MyFunctor myfunc(o);
myFunctor();
villintehaspam
  • 8,540
  • 6
  • 45
  • 76
  • How is using this functor different from calling `o.bar()` directly? – Manuel Feb 08 '10 at 22:48
  • @Manuel: The usage wouldn't be so trivial as to create a functor, then immediately call it. Rather you would then give the functor object to some other object that wouldn't know about what MyObj is or what the bar function does. It would only know that it has something with a ()-operator on it. – villintehaspam Feb 08 '10 at 22:51
  • @Manuel: It's not. Which is why I only said this gave me an idea. This is not the solution I was looking for. – John Dibling Feb 08 '10 at 22:52
  • @villintehaspam - And can't you think of a way to automatize this with boost::bind or something of the kind? – Manuel Feb 08 '10 at 22:55
  • @Manuel: Since a boost::bind would use a member function pointer, I'd assume that boost::bind would not work. – villintehaspam Feb 08 '10 at 23:33
  • @John Dibling: Is too!! :) The functor stores a reference to the MyObject object. You can give away the functor by copy to someone else, who by templating would be able to accept different types of functors. – villintehaspam Feb 08 '10 at 23:41
  • @Manuel: To clarify my last comment, the OP asked specifically not to do a typedef specifying the default parameter. I interpreted this as not wanting to specify the default parameter in a boost::bind either. – villintehaspam Feb 08 '10 at 23:46
  • @villintehaspam - And isn't it better to just wrap the call to `bar` with a free function and then turn this function into a functor with `bind`? Sounds like less boilerplate at least... – Manuel Feb 09 '10 at 08:01
  • @Manuel: Marginally less? I would agree with you that using boost::bind over self-written functors is normally better and signals intent in a better way. But when you would have to write an extra function anyway, I believe that the intent is more clear with a functor like above. I suspect this is highly subjective though. Me, I would probably either provide two versions of the bar()-function or provide the default argument as a enum-constant so that users of the class could use bar() without needing to use a magic number. And yes, I would use a boost::bind to wrap those calls if needed. :) – villintehaspam Feb 09 '10 at 09:37
2

This is not possible given the constraints. Your options are:

  1. Using function wrappers.
  2. Using Functors.

Check out Boost for some handy tools to simplify this.

user269044
  • 148
  • 5
  • I think that there's nothing in Boost that will prevent him from having to specify a value for the default argument one way or another. Check this related thread: http://stackoverflow.com/questions/2221832/boosttrim-each-string-in-stdvectorstdstring/2221871#2221871 – Manuel Feb 08 '10 at 22:44
1

Task: Suppose you have the following:

class Thing {
    public:
        void foo (int, double = 3.14) const {std::cout << "Thing::foo(int, double = 3.14) called.\n";}
        void goo (int, double = 1.5) const {std::cout << "Thing::goo(int, double = 1.5) called.\n";}
};

void function1 (const Thing& thing, int a, int b, double c) {
    // Code A
    thing.foo(a,c);
    // Code B
    thing.foo(b);
    // Code C
}

void function2 (const Thing& thing, int a, int b, double c) {
    // Code A
    thing.goo(a,c);
    // Code B
    thing.goo(b);
    // Code C
}

We want to write a helper function to capture function1 and function2 so that the repeated codes A, B, C need not be written twice.

The following will not compile:

class Thing {
    public:
        void foo (int, double = 3.14) const {std::cout << "Thing::foo(int, double = 3.14) called.\n";}
        void goo (int, double = 1.5) const {std::cout << "Thing::goo(int, double = 1.5) called.\n";}
};

void functionHelper (const Thing& thing, int a, int b, double c, void (Thing::*f)(int, double) const) {
    // Code A
    (thing.*f)(a,c);
    // Code B
//  (thing.*f)(b);  // Won't compile.  Too few arguments passed to (thing.*f), which expects (int, double).
    // Code C   
}

void function1 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, &Thing::foo);
}

void function2 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, &Thing::goo);
}

First solution (overload of Thing::foo and Thing::goo):

#include <iostream>

class Thing {
    public:
        void foo (int, double = 3.14) const {std::cout << "Thing::foo(int, double = 3.14) called.\n";}
        void foo_default (int a) const {
                std::cout << "Thing::foo_default(int) called.\n";
                foo(a);
        }
        void goo (int, double = 1.5) const {std::cout << "Thing::goo(int, double = 1.5) called.\n";}
        void goo_default (int a) const {
                std::cout << "Thing::goo_default(int) called.\n";
                goo(a);
        }
};

void functionHelper (const Thing& thing, int a, int b, double c,
        void (Thing::*f)(int, double) const, void (Thing::*g)(int) const) {
    // Code A
    (thing.*f)(a,c);
    // Code B
    (thing.*g)(b);  // This will compile now, since (thing.*g) expects int only as argument.
    // Code C   
}

void function1 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, &Thing::foo, &Thing::foo_default);
}

void function2 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, &Thing::goo, &Thing::goo_default);
}

int main() {
    Thing thing;
    function1 (thing, 2, 5, 1.8);
    std::cout << '\n';
    function2 (thing, 2, 5, 1.8);
}

Output:

Thing::foo(int, double = 3.14) called.
Thing::foo_default(int) called.
Thing::foo(int, double = 3.14) called.

Thing::goo(int, double = 1.5) called.
Thing::goo_default(int) called.
Thing::goo(int, double = 1.5) called.

Second solution (Wrap Thing::foo and Thing::goo into function objects):

#include <iostream>
#include <memory>

class Thing {
    public:
        void foo (int, double = 3.14) const {std::cout << "Thing::foo(int, double = 3.14) called.\n";}
        void goo (int, double = 1.5) const {std::cout << "Thing::goo(int, double = 1.5) called.\n";}
        
        class FooOrGoo {
            public: 
                void operator()(const Thing& thing, int a) const {helper1 (thing, a);}
                void operator()(const Thing& thing, int a, double b) {helper2 (thing, a, b);}
                virtual ~FooOrGoo() {std::cout << "Thing::FooOrGoo object destroyed.\n";}
            private:
                virtual void helper1 (const Thing& thing, int a) const = 0;
                virtual void helper2 (const Thing& thing, int a, double b) const = 0;
        };

        class Foo : public FooOrGoo {
            virtual void helper1 (const Thing& thing, int a) const override {thing.foo(a);}
            virtual void helper2 (const Thing& thing, int a, double b) const override {thing.foo(a, b);}
        };

        class Goo : public FooOrGoo {
            virtual void helper1 (const Thing& thing, int a) const override {thing.goo(a);}
            virtual void helper2 (const Thing& thing, int a, double b) const override {thing.goo(a, b);}
        };
};

void functionHelper (const Thing& thing, int a, int b, double c, std::unique_ptr<Thing::FooOrGoo> f) {
    // Code A
    (*f)(thing, a,c);
    // Code B
    (*f)(thing, b);
    // Code C   
}

void function1 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, std::unique_ptr<Thing::Foo>(new Thing::Foo));  // 'std::make_unique<Thing::Foo>());' is not supported by GCC 4.8.1.
}

void function2 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, std::unique_ptr<Thing::Goo>(new Thing::Goo));  // 'std::make_unique<Thing::Goo>());' is not supported by GCC 4.8.1.
}

int main() {
    Thing thing;
    function1 (thing, 2, 5, 1.8);
    std::cout << '\n';
    function2 (thing, 2, 5, 1.8);
}

Output:

Thing::foo(int, double = 3.14) called.
Thing::foo(int, double = 3.14) called.
Thing::FooOrGoo object destroyed.

Thing::goo(int, double = 1.5) called.
Thing::goo(int, double = 1.5) called.
Thing::FooOrGoo object destroyed.

Which solution do you think is better? I think the second one is more elegant, but there are more lines of code (I couldn't do it without polymorphism).

Waqar
  • 8,558
  • 4
  • 35
  • 43
prestokeys
  • 4,817
  • 3
  • 20
  • 43