Build function parameters with variadic templates

2020-07-17 07:33发布

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.

3条回答
姐就是有狂的资本
2楼-- · 2020-07-17 07:54

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
查看更多
ら.Afraid
3楼-- · 2020-07-17 08:01

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
查看更多
Rolldiameter
4楼-- · 2020-07-17 08:02

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);
 }
查看更多
登录 后发表回答