-1

Let's assume I have the structure X declared and defined like this:

struct X {
    int i;
    X(int i = 0) : i(i) { std::cout << "X(int i = " << i << "):   X[" << this << "]" << std::endl; }
    int multiply(int a, int b = 1) { return i * a * b; }
};

I want to call X::multiply via function-pointer. To do this, I defined a proxy-function:

template <typename ObjectType,
          typename ReturnType,
          typename... ParameterTypes>
ReturnType proxy(ObjectType * object,
                 ReturnType (ObjectType::* func)(ParameterTypes...),
                 ParameterTypes... params) {
    return (object->*func)(params...);
}

I use it like this:

int main() {
    X x(10);
    int a = 5;
    
    int r = proxy<X, int, int, int>(&x, &X::multiply, a, 2);         // proxy<...> works
    //int r = proxy(&x, &X::multiply, a, 2);                         // proxy works
    //int r = proxy2<X, int, int, int, int>(&x, &X::multiply, a, 2); // proxy2<...> does not work
    //int r = proxy2(&x, &X::multiply, a, 2);                        // proxy2 works

    std::cout << "Proxy-Test:"
              << "\na = " << 5
              << "\nr = " << r
              << std::endl;
}

proxy<...> and even proxy work like a charm, except that I cannot call proxy with only one argument although the second one should be optional. (Leaving out the right-value-argument 2 in proxy's invocation will yield an error.) Of course, proxy is expecting exactly two arguments, so I also created another version of the proxy-function:

template <typename ObjectType,
          typename ReturnType,
          typename... ParameterTypes,
          typename... ArgumentTypes>
ReturnType proxy4(ObjectType * object,
                  ReturnType (ObjectType::* func)(ParameterTypes...),
                  ArgumentTypes... args) {
    return (object->*func)(args...);
}

This version has its types for parameters and arguments split into separate parameter-packs. Weirdly enough, invoking proxy2<X, int, int, int, int>(&x, &X::multiply, a, 2); makes the compiler think this invocation's ParameterTypes are (int, int, int) while ArgumentTypes are (int, int). Equally weird is that the invocation proxy2(&x, &X::multiply, a, 2); works just fine. None of those work if I leave out the right-value 2.

The problem remains: How can I call X::multiply with only the a-parameter, therefore without specifying b?

ChaosNe0
  • 37
  • 7
  • 1
    The second argument is only optional if you call the function by name. It is not optional if you do any kind of indirection. – n. m. could be an AI Jul 07 '21 at 09:44
  • "I want to call X::multiply via function-pointer." why? I am serious; why does your problem require that specific implementation detail in the solution? Is this a school assignment, or do you have an underlyong problem and think this is a solution to it? – Yakk - Adam Nevraumont Jul 07 '21 at 11:11
  • @Yakk-AdamNevraumont I think this is the underlying solution to my less minimal problem: I use a framework for a web interface and I want to make a function that can take a layout-object-pointer and call its "addWidget"-function. But: Depending on the type of layout, this function needs additional parameters (for example when adding something into a grid: it needs at least a rowId and a columnId). I also need to create a new unique-ptr in that function, so I made a more general function which invokes a function-pointer and a couple of functions which call this function. – ChaosNe0 Jul 07 '21 at 11:19
  • 1
    Ah; then don't use a pmf, just use a lambda. – Yakk - Adam Nevraumont Jul 07 '21 at 11:22

2 Answers2

1

Default arguments cannot be used with calls via function pointers (pmf or otherwise) because default arguments are not a part of the function type and thus not a part of any function pointer.

A simple example using no templates or pmf:

bool getFlag();
int foo(int, int);
int bar(int, int=42);
bool flag = getFlag();
auto pf = flag ? foo : bar;

int answer1 = pf(5, 6); // ok
int answer2 = pf(5);    // ?????

The last line cannot be statically typechecked because its validity depends on the run-time value of flag. There are two viable options for any language: (1) declare the line unconditionally invalid of (2) check it at run-time. C++ as a statically typed language chooses the first option. There is no way around it. Templates or pmf do not change this fact.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
1

How can I call X::multiply with only the a-parameter, therefore without specifying b?

You can't, intercepting the type of X::multiply and the types of it's arguments.

The problem is default values for functions/methods aren't part of the function signature, so when you pass a X::multiply pointer, the type of the method is int (X::*)(int, int) and the fact that the second argument is optional (with a 1 default value) is lost.

So, inside proxy()/proxy2(), you can't call X::multiply() with only an argument, because the compiler know that X::multiply() expects two arguments.

The usual way to solve this problem, as suggested by Yakk - Adam Nevraumont, is pass through a lambda function. This permit to use the multiply() method directly, avoiding it's type deduction and maintaining the knowledge of the optional argument (and of the default value).

For example, if you don't need to pass the x object but you can pass it inside the lambda, you can write a simple proxy_1() function

template <typename Lambda,
          typename ... ArgumentTypes>
auto proxy_1 (Lambda func,
              ArgumentTypes ... args)
 { return func(args...); }

that can be used as follows

   X   x{10};
   int a{5};

   auto l1 { [&](auto... args) { return x.multiply(args...); } };

   int r1a { proxy_1(l1, a, 2) };
   int r1b { proxy_1(l1, a) };
   int r1c { proxy_1<decltype(l1), int, int>(l1, a, 2) };
   int r1d { proxy_1<decltype(l1), int>(l1, a) };

On the contrary, if you need to pass function and object as separate arguments, the proxy_2() become a little more complicated

template <typename Object,
          typename Lambda,
          typename ... ArgumentTypes>
auto proxy_2 (Object obj,
              Lambda func,
              ArgumentTypes ... args)
 { return func(obj, args...); }

and the lambda that you have to write receive the object as an argument

   X   x{10};
   int a{5};

   auto l2 { [](auto obj, auto... args) { return obj.multiply(args...); } };

   int r2a { proxy_2(x, l2, a, 2) };
   int r2b { proxy_2(x, l2, a) };
   int r2c { proxy_2<X, decltype(l2), int, int>(x, l2, a, 2) };
   int r2d { proxy_2<X, decltype(l2), int>(x, l2, a) };

The following is a full compiling C++14 example

#include <iostream>

struct X
 {
   int i;

   X (int i0 = 0) : i{i0}
    { std::cout << "X(int i = " << i << "):   X[" << this << "]" << std::endl; }

   int multiply (int a, int b = 1)
    { return i * a * b; }
 };

template <typename Lambda,
          typename ... ArgumentTypes>
auto proxy_1 (Lambda func,
              ArgumentTypes ... args)
 { return func(args...); }

template <typename Object,
          typename Lambda,
          typename ... ArgumentTypes>
auto proxy_2 (Object obj,
              Lambda func,
              ArgumentTypes ... args)
 { return func(obj, args...); }

int main()
 {
   X   x{10};
   int a{5};
     
   auto l1 { [&](auto... args) { return x.multiply(args...); } };

   int r1a { proxy_1(l1, a, 2) };
   int r1b { proxy_1(l1, a) };
   int r1c { proxy_1<decltype(l1), int, int>(l1, a, 2) };
   int r1d { proxy_1<decltype(l1), int>(l1, a) };

   auto l2 { [](auto obj, auto... args) { return obj.multiply(args...); } };

   int r2a { proxy_2(x, l2, a, 2) };
   int r2b { proxy_2(x, l2, a) };
   int r2c { proxy_2<X, decltype(l2), int, int>(x, l2, a, 2) };
   int r2d { proxy_2<X, decltype(l2), int>(x, l2, a) };

   std::cout << "Proxy-Test:"
      << "\na  = " << 5
      << "\nr1a = " << r1a
      << "\nr1b = " << r1b
      << "\nr1c = " << r1c
      << "\nr1d = " << r1d
      << "\nr2a = " << r2a
      << "\nr2b = " << r2b
      << "\nr2c = " << r2c
      << "\nr2d = " << r2d
      << std::endl;
 }
max66
  • 65,235
  • 10
  • 71
  • 111