2

This question is related to one on SO a couple of years ago by Georg Fritzsche about transforming a parameter pack (Is it possible to transform the types in a parameter pack?). In the end, the individual types in the parameter pack can be transformed, e.g. by converting to corresponding pointer types.

I am wondering if it is possible to use this technique to write one standard function/functor and a set of wrapper functions (out of one template), so that the wrappers can take parameters of equivalent types and then call the standard function to do actual work.

Using the answer by Johannes Schaub - litb the original example below. Is it possible to write one template f, which can take any combinations of int/int*,char/char* and call a common function f_std(int*,char*) to do the work. (The number of parameters is not pre-specified.)

--- Update --- For example, given int i; char c;, is it possible to write a caller using pack transformation such that the following works

  call_ptr(f_std,i,c);
  call_ptr(f_std,&i,c);
  call_ptr(f_std,i,&c);

What I've tried so far is listed below (updated to clarify.). Basically, I tried to accept an list of not necessarily pointer types and convert them to pointer types, before making call to a std::function that takes pointer types. But the code does not compile. I don't know how to write a helper function to accept one function with a standard signature, but accept a parameter pack of something else.

Thanks in advance

#include <type_traits>
#include <functional>
using namespace std;

template<class... Args> struct X {};
template<class T> struct make_pointer     { typedef T* type; };
template<class T> struct make_pointer<T*> { typedef T* type; };

template<template<typename...> class List, 
         template<typename> class Mod, 
         typename ...Args>
struct magic {
    typedef List<typename Mod<Args>::type...> type;
};

/////////////////
// trying to convert parameter pack to pointers
template<class T> T* make_ptr(T x) { return &x; }
template<class T> T* make_ptr(T* x) { return x; }  
template <typename Value, typename ...Args>
class ByPtrFunc
{
public:
  typedef typename magic<X, make_pointer, Args...>::type PArgs;
  Value operator()(Args... args) { return f(make_ptr(args)...);  }

private:
  std::function<Value (PArgs...)> _ptr_func;

}; //ByPtrFunc

//helper function to make call
template<typename A, typename ...Args>
static A call_ptr(std::function<A (Args...)> f, Args... args) {
  return ByPtrFunc<A, Args...>{f}(args ...);
}

int main() {
  typedef magic<X, make_pointer, int*, char>::type A;
  typedef X<int*, char*> B;
  static_assert(is_same<A, B>::value, ":(");

  int i=0; char c='c';
  function<int (int* pa,char* pb)> f_std = [](int* pa,char* pb)->int {return *pa + * pb;};
  f_std(&i,&c);
  //////////////////
  //Is the following possible.
  call_ptr(f_std,i,c);
  call_ptr(f_std,&i,c);
  call_ptr(f_std,i,&c);

  return 0;
}
Community
  • 1
  • 1
thor
  • 21,418
  • 31
  • 87
  • 173
  • how many arguments can f take? is it variadic or just 2? You can check if the argument is a pointer using [std::is_pointer](http://en.cppreference.com/w/cpp/types/is_pointer) and then add the pointer if it isn't. – Gasim Jan 28 '14 at 06:00
  • @Gasim variadic, to be interesting. – thor Jan 28 '14 at 06:08
  • I think what you're asking is impossible because transforming types will prevent template parameter deduction and hence prevent overload resolution. But maybe someone cleverer than me will come up with a workaround. – Brian Bi Jan 28 '14 at 18:01

3 Answers3

3

This answers your question syntax-wise, if I've understood it correctly: yes, it's possible.

// given int or char lvalue, returns its address
template<class T>
T* transform(T& t) {
    return &t;
}

// given int* or char*, simply returns the value itself
template<class T>
T* transform(T* t) {
    return t;
}

// prints out the address corresponding to each of its arguments    
void f_std() {
}

template<class Arg, class... Args>
void f_std(Arg arg, Args... args) {
    std::cout << (void*)arg << std::endl;
    f_std(args...);
}

// converts int to int*, char to char*, then calls f_std
template<class... Args>
void f(Args... args) {
    f_std(transform(args)...);
}

Unfortunately, calling f will pass int and char arguments by value, and hence copy them. To fix this, use perfect forwarding in the definition of f:

template<class... Args>
void f(Args&&... args) {
    f_std(transform(std::forward<Args>(args))...);
}

Driver:

int main() {
    int x = 1;
    char c = 'a';
    cout << (void*)&x << endl;
    cout << (void*)&c << endl;
    f(x, &x, c, &c);
}

Output (example; ran it on my machine just now):

0x7fff36fb5ebc
0x7fff36fb5ebb
0x7fff36fb5ebc
0x7fff36fb5ebc
0x7fff36fb5ebb
0x7fff36fb5ebb
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • `template void f_std(Arg arg, Args... args)` that looks like a partial specialization in its context, but it's an overload. You don't need `template void f_std(Args... args);` and specialize that to `template<> void f_std()`, a simple `void f_std()` overload should be sufficient. – dyp Jan 28 '14 at 09:58
  • Note: `std::ostream` has an overload [`operator<<(const void*)`](http://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt) to print addresses. – dyp Jan 28 '14 at 10:01
  • @Brian. +1 Thanks for your answer. If I understand it correctly, your answer works for any fixed `f_std`. I guess I needed a more general solution in which `f_std` is a parameter as well for a higher function like `call_ptr(f_std,i,&c)`. That's why I am interested in translating the types. Please see update. – thor Jan 28 '14 at 13:44
  • @dyp: I think he uses it that way to avoid the use of `operator<<(const char*)`. – Jarod42 Jan 28 '14 at 13:45
  • @Jarod42 Then, why not explicitly convert to `void*`? `std::cout << static_cast(arg) << "\n";` – dyp Jan 28 '14 at 13:50
  • @dyp Oh yeah, I'm a bit rusty on the variadic function thing, so thanks for pointing that out. – Brian Bi Jan 28 '14 at 15:50
2

Following may help:

template <typename T> T* make_pointer(T& t) { return &t; }
template <typename T> T* make_pointer(T* t) { return t; }

template <typename Ret, typename... Args, typename ...Ts>
Ret call_ptr(std::function<Ret (Args*...)> f, Ts&&...args)
{
    static_assert(sizeof...(Args) == sizeof...(Ts), "Bad parameters");
    f(make_pointer(std::forward<Ts>(args))...);
}

Now, use it:

void f_std(int*, char*) { /* Your code */ }

int main(int argc, char *argv[])
{
    int i;
    char c;

    std::function<void (int*, char*)> f1 = f_std;

    call_ptr(f1, i, c);
    call_ptr(f1, i, &c);
    call_ptr(f1, &i, c);
    call_ptr(f1, &i, &c);

    return 0;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 1
    +1. Thanks. This is really close to what I am looking for. I didn't know that we can use two parameter packs in one template<> statement. (Thought it requires nested types.) On the other hand, is it possible to make this more general and use a general type transformation mechanism? I am really looking for a way to do type conversions other than add/remove pointer or reference to a type. – thor Jan 28 '14 at 19:46
  • @TingL: You may use a `class` *Functor* to do any conversion, something like `struct make_pointer { template operator() (T*) const; template operator() (T&) const; };` and then add an other template parameter to `call_ptr`. – Jarod42 Jan 29 '14 at 14:12
0

For reference, below is what worked for me, based on the accepted answer @Jarod42 and the type transformation "magic". slightly more general and with added type checking. Turns out type-transformation is simply a pattern expansion.

#include <type_traits>
#include <functional>
#include <iostream>
using namespace std;

/////////////////
// convert parameter pack to pointers
//types
template<class T> struct make_ptr_t     { typedef T* type; };
template<class T> struct make_ptr_t<T*> { typedef T* type; };
//values
template<class T> T* make_ptr(T& x) { return &x; }
template<class T> T* make_ptr(T* x) { return x; }

/////////////////////////////////////
// (optional) only for type checking
template<class... Args> struct X {};
template<template<typename...> class List, 
         template<typename> class Mod, 
         typename ...Args>
struct magic {
    typedef List<typename Mod<Args>::type...> type;
};

//helper function to make call
template<typename A, typename ...PArgs, typename ...Args>
static A call_ptr(std::function<A (PArgs...)> f, Args... args) {
  static_assert(is_same<X<PArgs...>,typename magic<X, make_ptr_t, Args...>::type>::value, "Bad parameters for f in call_ptr()"); //type checking
  return f(make_ptr(args)...);
}

int main() {
  int i=0; char c='c'; string s="c";
  function<int (int* pa,char* pb)> f_std = [](int* pa,char* pb)->int {return *pa + * pb;};
  f_std(&i,&c);

  cout << call_ptr(f_std,i,c) << endl;
  cout << call_ptr(f_std,&i,c) << endl;
  cout << call_ptr(f_std,i,&c) << endl;
  //cout << call_ptr(f_std,i,s) << endl; //complains about bad parameters.
  return 0;
}
thor
  • 21,418
  • 31
  • 87
  • 173