4

I have this sample code, which do what I need for a 3-parameter function :

template<typename T>T GETPARAM(void) { return T(); }
template<>int GETPARAM(void) { return 123; }
template<>double GETPARAM(void) { return 1.2345; }
template<>const char *GETPARAM(void) { return "hello"; }

template<typename P1, typename P2, typename P3, typename RES> RES BuildArgs3(RES(*fn)(P1, P2, P3)) {

    P1 p1 = GETPARAM<P1>();
    P2 p2 = GETPARAM<P2>();
    P3 p3 = GETPARAM<P3>();
    return fn(p1, p2, p3);
}

int print3(int a, double b, const char *c)
{
    Cout() << "Print3:" << a << ", " << b << ", " << c << "\n";
    return 1;
}

main() {
    BuildArgs3(print3);
}

(the GETPARAM templates are there just to show the call).

I tried to generalize it with a variadic template for functions with any number of arguments with no success. Is it possible ?

The template shall be useable for any T (*fn)(P1, P2, ...) with any return type and any number of parameters, building the parameters on the fly calling the GETPARAM<Pn>() for each of them.

It's needed to create a binding system for a scripting language, fetching parameters from a stack and calling a C++ function when done.

max66
  • 65,235
  • 10
  • 71
  • 111
Max
  • 101
  • 8

3 Answers3

4

I tried to generalize it with a variadic template for functions with any number of arguments with no success. Is it possible ?

Yes; and it's simple

template <typename R, typename ... Args>
R BuildArgsN (R(*fn)(Args...))
 { return fn(GETPARAM<Args>()...); }

The following is a full working example

#include <iostream>

template<typename T>T GETPARAM(void) { return T(); }
template<>int GETPARAM(void) { return 123; }
template<>double GETPARAM(void) { return 1.2345; }
template<>const char *GETPARAM(void) { return "hello"; }

template <typename R, typename ... Args>
R BuildArgsN (R(*fn)(Args...))
 { return fn(GETPARAM<Args>()...); }

int print3 (int a, double b, char const * c)
 {
   std::cout << "Print3:" << a << ", " << b << ", " << c << "\n";

   return 1;
 }

int main ()
 {
   BuildArgsN(print3);
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Thanks, it works perfectly! Just another question... as I'm getting the values from a stack, I need to get them in reversed order. The first GETPARAM() brings me last function parameter. Is there a simple way to get them in the correct order ? – Max Nov 09 '17 at 17:47
  • it's difficult to respond your new question without knowing your data structures; the simpler approach I can imagine is substitute the stack with a queue (maybe `std::queue` based) – max66 Nov 09 '17 at 17:54
  • No, I can't. The stack is there... what I can do is just to fetch the values with my GETPARAM(), and they're in reversed order. If my function is test(a, b, c) what I get from stack with your nice code is 'c', then 'b' and finally 'a', so the function gets called with reversed parameter order. One solution could be to count the parameters with another template and then setup the GETPARAM() to start fetching from bottom, which is possible... is there a way to count function parameters ? I know only how to count template arguments. – Max Nov 09 '17 at 18:09
  • Attention: In general, there is no guarantee on the order of evaluation of function arguments. If `GETPARAM` is not pure then you need an approach which guarantees that the calls are ordered properly. See [my answer](https://stackoverflow.com/a/47217995/2615118). – Julius Nov 10 '17 at 15:47
2

If the calls to GETPARAM should be ordered then you have to expand the variadic pack in a context that guarantees a particular order. One option is list initialization:

Every initializer clause is sequenced before any initializer clause that follows it in the braced-init-list. This is in contrast with the arguments of a function call expression, which are unsequenced.

Let us consider your given example: You can expand the argument pack yielding the GETPARAM calls inside the curly braces constructing a proxy object. The proxy object can be implicitly convertible to the return type of your function.

#include <iostream>

int pos = 0;// DEBUG: observe the order of `GETPARAM` calls

template<typename T>T GETPARAM();

template<>
int GETPARAM() { return 100 + pos++; }

template<>
double GETPARAM() { return 100.5 + pos++; }

template<>
const char* GETPARAM() { pos++; return "hello"; }

////////////////////////////////////////////////////////////////////////////////

template<class Ret>
struct ArgEvalOrderer {
  Ret ret;

  template<class... Args>
  ArgEvalOrderer(
    Ret(*f)(Args...),
    Args... args
  )
    : ret{f(args...)}
  {}

  operator Ret() const { return ret; }
};

template<class Ret, class... Args>
Ret call_after_ordered_argfetch(Ret(*f)(Args...)) {
// evaluation order guaranteed by braced init list
  return ArgEvalOrderer<Ret>{f, GETPARAM<Args>()...};
}

template<class Ret, class... Args>
Ret call_after_ordered_argfetch_buggy(Ret(*f)(Args...)) {
// BUGGY: NO GUARANTEE on evaluation order
  return ArgEvalOrderer<Ret>(f, GETPARAM<Args>()...);
}

template<class Ret, class... Args>
Ret call_after_unordered_argfetch(Ret(*f)(Args...)) {
// BUGGY: NO GUARANTEE on evaluation order
  return f(GETPARAM<Args>()...);
}

int print7(int a, int b, double c, int d, double e, const char* f, double g) {
  std::cout << "print7: " << a
        << ", " << b
        << ", " << c
        << ", " << d
        << ", " << e
        << ", " << f
        << ", " << g
        << std::endl;

  return 1;
}

int main() {
  call_after_ordered_argfetch(print7);
  call_after_ordered_argfetch_buggy(print7);
  call_after_unordered_argfetch(print7);

  return 0;
}

Note that the upper version is the only one which guarantees ordered evaluation. In fact, I observe (online demo) the following output:

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

print7: 100, 101, 102.5, 103, 104.5, hello, 106.5

print7: 113, 112, 111.5, 110, 109.5, hello, 107.5

print7: 120, 119, 118.5, 117, 116.5, hello, 114.5
Julius
  • 1,816
  • 10
  • 14
  • Do you mean that in fn(GETPARAM()...); there is no guarantee thet Args are processed left to right ? Because I used a solution that adds an int& (sort of) stack pointer, initialized before first call to stack bottom and it seems to work. – Max Nov 10 '17 at 11:08
  • @Max: Here is the way I understand it: `fn(GETPARAM()...)` becomes, say, `fn(GETPARAM(), GETPARAM(), GETPARAM())` (in the expected order). **However**, there is no guarantee on the order of evaluation of the `GETPARAM` calls as function arguments to `fn`. It may work by accident (as you observe). Note that this issue has nothing to do with variadic templates, but with **any** arguments to function calls. See also other questions, e.g., [this one](https://stackoverflow.com/questions/42047795/order-of-parameter-pack-expansion). – Julius Nov 10 '17 at 15:43
  • @Julius, and if the `int print7()` example function returns void? It cannot create a class attribute of void type. – Evandro Coan Nov 02 '18 at 20:18
  • @user: There is no problem in this example if `print7` returns void. If `GETPARAM` returns void then there is a problem, but this question does not make sense in that case. – Julius Nov 03 '18 at 07:40
  • @user: Generally (if you just would like to call some functions and ignore the return) then the simplest way reads `(funcs_which_may_return_void(), ...);`. It is a C++17 [fold expression](https://en.cppreference.com/w/cpp/language/fold) over the comma operator and the evaluation order is guaranteed. If C++17 is not available there is this workaround: `int dummy[] = {0, ((void)(funcs_which_may_return_void()), 1)...};`. Here, the comma operator is used to "replace" each return by `1`. In order to suppress compiler warnings you may want to add a second line with `(void)dummy;`. – Julius Nov 03 '18 at 07:48
0

Here is a code for statically building any function with any number of arguments. It Is completely independent from any compiler as and libraries as or (excepting the printf's used, but you can remove them safely). But requires because of the variadic templates.

#include <stdio.h>

// SIZEOF Type Package
template<typename ... Tn>
struct SIZEOF
{ static const unsigned int Result = 0; };

template<typename T1, typename ... Tn>
struct SIZEOF<T1, Tn ...>
{ static const unsigned int Result = sizeof(T1) + SIZEOF<Tn ...>::Result ; };

template<int ...>
struct MetaSequenceOfIntegers { };

template<int AccumulatedSize, typename Tn, int... GeneratedSequence>
struct GeneratorOfIntegerSequence;

template<
            int AccumulatedSize, 
            typename Grouper, 
            typename Head, 
            typename... Tail, 
            int... GeneratedSequence
        >
struct GeneratorOfIntegerSequence< 
        AccumulatedSize, Grouper( Head, Tail... ), GeneratedSequence... >
{
    typedef typename GeneratorOfIntegerSequence
            < 
                AccumulatedSize + sizeof(Head), 
                Grouper( Tail... ), 
                GeneratedSequence..., 
                AccumulatedSize
            >::type type;
};

template<int AccumulatedSize, typename Grouper, int... GeneratedSequence>
struct GeneratorOfIntegerSequence<AccumulatedSize, Grouper(), GeneratedSequence...>
{
  typedef MetaSequenceOfIntegers<GeneratedSequence...> type;
};

template<typename Tn>
class Closure;

template<typename ReturnType, typename... Tn>
class Closure<ReturnType( Tn... )>
{
public:
    typedef ReturnType(*Function)(Tn ...);
    static const unsigned int PARAMETERS_COUNT = sizeof...( Tn );
    static const unsigned int PARAMETERS_LENGTH = SIZEOF<Tn ...>::Result;

private:
    Function _entry;
    char* _parameters;

public:
    Closure(Function _entry, Tn ... an): _entry(_entry)
    {
        printf( "Closure::Closure(_entry=%d, PARAMETERS_COUNT=%d, 
                PARAMETERS_LENGTH=%d, sizeof=%d) => %d\n",
                &_entry, PARAMETERS_COUNT, PARAMETERS_LENGTH, sizeof(*this), this );

        if(PARAMETERS_LENGTH) _parameters = new char[PARAMETERS_LENGTH];
        pack_helper( _parameters, an ... );
    }

    ~Closure() {
        printf( "Closure::~Closure(this=%d, _entry=%d,
                PARAMETERS_COUNT=%d, PARAMETERS_LENGTH=%d, sizeof=%d)\n",
                this, &_entry, PARAMETERS_COUNT, PARAMETERS_LENGTH, sizeof(*this) );

        if(PARAMETERS_LENGTH) delete _parameters;
    }

    ReturnType operator()() {
        return _run( typename GeneratorOfIntegerSequence< 0, int(Tn...) >::type() );
    }

private:
    template<int ...Sequence>
    ReturnType _run(MetaSequenceOfIntegers<Sequence...>)
    {
        printf( "Closure::_run(this=%d)\n", this );
        return _entry( unpack_helper<Sequence, Tn>()... );
    }

    template<const int position, typename T>
    T unpack_helper()
    {
        printf( "Closure::unpack_helper(Head=%d, address=%d(%d), position=%d)\n",
                sizeof( T ), _parameters + position, _parameters, position );

        return *reinterpret_cast<T *>( _parameters + position );
    }

public:
    template<typename Head, typename ... Tail>
    static void pack_helper(char* pointer_address, Head head, Tail ... tail)
    {
        printf( "Closure::pack_helper(
                Head=%d, address=%d)\n", sizeof( Head ), pointer_address );

        *reinterpret_cast<Head *>(pointer_address) = head;
        pack_helper(pointer_address + sizeof( Head ), tail ...);
    }

    static void pack_helper(char* pointer_address) {}
};

/**
 * Create a closure which can have any return type.
 */
template<typename ReturnType, typename ... Tn>
Closure< ReturnType(Tn ...) > create_closure( 
        ReturnType(*_entry)( Tn ... ), Tn ... an )
{
    auto closure = new Closure< ReturnType(Tn ...) >( _entry, an ... );
    printf( "create_closure=%d\n", closure );
    return *closure;
}

char test_function1(char arg1, int arg2, bool arg3) {
    printf("   test_function1: %c, %d, %d\n", arg1, arg2, arg3);
}

char test_function2(const char* arg1, const char* arg2, char arg3) {
    printf("   test_function2: %s, %s, %c\n", arg1, arg2, arg3);
}

char test_function3() {
    printf("   test_function3\n");
}

void test_function4() {
    printf("   test_function4\n");
}

void test_function5(const char* arg1) {
    printf("   test_function5=%s\n", arg1);
}

template<typename ... Tn>
void test_closure(Tn ... an) {
    auto closure = create_closure(an ...);
    closure();
    printf( "\n" );
}

// clang++ -Xclang -ast-print -fsyntax-only test.cpp > expanded.cpp
int main()
{
    test_closure( &test_function1, 'a', 10, false );
    test_closure( &test_function2, "test1", "test2", 'b' );
    test_closure( &test_function3 );
    test_closure( &test_function4 );
    test_closure( &test_function5, "Testa 3" );
    test_closure( &test_function5, "Testa 4" );
}

Running it you will see the tests results:

$ g++ -o test test_variadic_critical_section_dynamic.cpp && ./test
Closure::Closure(_entry=-13672, 
        PARAMETERS_COUNT=3, PARAMETERS_LENGTH=6, sizeof=16) => 164864
Closure::pack_helper(Head=1, address=164976)
Closure::pack_helper(Head=4, address=164977)
Closure::pack_helper(Head=1, address=164981)
create_closure=164864
Closure::_run(this=-13520)
Closure::unpack_helper(Head=1, address=164981(164976), position=5)
Closure::unpack_helper(Head=4, address=164977(164976), position=1)
Closure::unpack_helper(Head=1, address=164976(164976), position=0)
   test_function1: a, 10, 0

Closure::~Closure(this=-13520, _entry=-13520, 
        PARAMETERS_COUNT=3, PARAMETERS_LENGTH=6, sizeof=16)
Closure::Closure(_entry=-13672, 
        PARAMETERS_COUNT=3, PARAMETERS_LENGTH=17, sizeof=16) => 164976
Closure::pack_helper(Head=8, address=165008)
Closure::pack_helper(Head=8, address=165016)
Closure::pack_helper(Head=1, address=165024)
create_closure=164976
Closure::_run(this=-13520)
Closure::unpack_helper(Head=1, address=165024(165008), position=16)
Closure::unpack_helper(Head=8, address=165016(165008), position=8)
Closure::unpack_helper(Head=8, address=165008(165008), position=0)
   test_function2: test1, test2, b

Closure::~Closure(this=-13520, _entry=-13520, 
        PARAMETERS_COUNT=3, PARAMETERS_LENGTH=17, sizeof=16)
Closure::Closure(_entry=-13624, 
        PARAMETERS_COUNT=0, PARAMETERS_LENGTH=0, sizeof=16) => 165008
create_closure=165008
Closure::_run(this=-13520)
   test_function3

Closure::~Closure(this=-13520, _entry=-13520, 
        PARAMETERS_COUNT=0, PARAMETERS_LENGTH=0, sizeof=16)
Closure::Closure(_entry=-13624, 
        PARAMETERS_COUNT=0, PARAMETERS_LENGTH=0, sizeof=16) => 165040
create_closure=165040
Closure::_run(this=-13520)
   test_function4

Closure::~Closure(this=-13520, _entry=-13520, 
        PARAMETERS_COUNT=0, PARAMETERS_LENGTH=0, sizeof=16)
Closure::Closure(_entry=-13624, 
        PARAMETERS_COUNT=1, PARAMETERS_LENGTH=8, sizeof=16) => 165072
Closure::pack_helper(Head=8, address=609568)
create_closure=165072
Closure::_run(this=-13520)
Closure::unpack_helper(Head=8, address=609568(609568), position=0)
   test_function5=Testa 3

Closure::~Closure(this=-13520, _entry=-13520, 
        PARAMETERS_COUNT=1, PARAMETERS_LENGTH=8, sizeof=16)
Closure::Closure(_entry=-13624, 
        PARAMETERS_COUNT=1, PARAMETERS_LENGTH=8, sizeof=16) => 609568
Closure::pack_helper(Head=8, address=609600)
create_closure=609568
Closure::_run(this=-13520)
Closure::unpack_helper(Head=8, address=609600(609600), position=0)
   test_function5=Testa 4

Closure::~Closure(this=-13520, _entry=-13520, 
        PARAMETERS_COUNT=1, PARAMETERS_LENGTH=8, sizeof=16)

You can run it with clang++ to see the generated template code:

$ clang++ -Xclang -ast-print -fsyntax-only test.cpp > expanded.cpp
// ...
private:
    template<> char _run<<0, 8, 16>>(MetaSequenceOfIntegers<0, 8, 16>) 
    {
        return this->_entry(
            this->unpack_helper<0, const char *>(), 
            this->unpack_helper<8, const char *>(), 
            this->unpack_helper<16, char>()
        );
    }

    template<> const char *unpack_helper<0, const char *>() 
    {
        return *reinterpret_cast<const char **>(this->_parameters + 0);
    }

    template<> const char *unpack_helper<8, const char *>() {
        return *reinterpret_cast<const char **>(this->_parameters + 8);
    }

    template<> char unpack_helper<16, char>() {
        return *reinterpret_cast<char *>(this->_parameters + 16);
    }
// ...

References

  1. How to reverse an integer parameter pack?
  2. Can we see the template instantiated code by C++ compiler
  3. Variadic templates, parameter pack and its discussed ambiguity in a parameter list
  4. "unpacking" a tuple to call a matching function pointer
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144