【问题标题】:Build function parameters with variadic templates使用可变参数模板构建函数参数
【发布时间】:2017-11-09 17:00:18
【问题描述】:

我有这个示例代码,它可以满足我对 3 参数函数的需求:

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);
}

(GETPARAM 模板只是为了显示调用)。

我尝试使用可变参数模板对具有任意数量参数的函数进行概括,但没有成功。有可能吗?

该模板应可用于具有任何返回类型和任意数量参数的任何T (*fn)(P1, P2, ...),并为每个参数动态调用GETPARAM&lt;Pn&gt;()。

需要为脚本语言创建绑定系统,从堆栈中获取参数并在完成后调用 C++ 函数。

【问题讨论】:

    标签: c++ c++11 templates variadic-templates


    【解决方案1】:

    我尝试使用可变参数模板对具有任意数量参数的函数进行概括,但没有成功。有可能吗?

    是的;而且很简单

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

    以下是一个完整的工作示例

    #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);
     }
    

    【讨论】:

    • 谢谢,它工作得很好!只是另一个问题......当我从堆栈中获取值时,我需要以相反的顺序获取它们。第一个 GETPARAM() 给我带来了最后一个函数参数。有没有一种简单的方法可以让它们按正确的顺序排列?
    • 在不了解您的数据结构的情况下很难回答您的新问题;我能想象的更简单的方法是用队列替换堆栈(可能基于std::queue)
    • 不,我不能。堆栈在那里......我能做的就是用我的 GETPARAM() 获取值,它们的顺序是相反的。如果我的函数是 test(a, b, c) 我从堆栈中得到的代码是'c',然后是'b',最后是'a',所以函数会以相反的参数顺序被调用。一种解决方案可能是使用另一个模板计算参数,然后设置 GETPARAM() 以从底部开始获取,这是可能的......有没有办法计算函数参数?我只知道如何计算模板参数。
    • 注意:一般来说,函数参数的求值顺序是没有保证的。如果GETPARAM 不纯,那么您需要一种方法来保证调用正确排序。见my answer。
    【解决方案2】:

    如果应该对GETPARAM 的调用进行排序,那么您必须在保证特定顺序的上下文中扩展可变参数包。一种选择是list initialization:

    每个初始化子句都排在任何初始化子句之前 在括号初始化列表中紧随其后。这与 函数调用表达式的参数,它们是无序的。

    让我们考虑您给定的示例:您可以扩展参数包以产生GETPARAM 调用在构造代理对象的大括号内。代理对象可以隐式转换为函数的返回类型。

    #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;
    }
    

    请注意,上层版本是唯一保证有序评估的版本。其实I observe (online demo)输出如下:

    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
    

    【讨论】:

    • 你的意思是在 fn(GETPARAM()...);不能保证从左到右处理 Args 吗?因为我使用了一个解决方案,它添加了一个 int&(某种)堆栈指针,在第一次调用堆栈底部之前进行了初始化,它似乎可以工作。
    • @Max:我的理解是这样的:fn(GETPARAM&lt;Args&gt;()...) 变成了,比如说,fn(GETPARAM&lt;Args0&gt;(), GETPARAM&lt;Args1&gt;(), GETPARAM&lt;Args2&gt;())(按照预期的顺序)。 但是,不能保证 GETPARAM 调用作为 fn 的函数参数的评估顺序。它可能会意外工作(如您所见)。请注意,此问题与可变参数模板无关,而是与函数调用的 any 参数有关。另请参阅其他问题,例如,this one。
    • @Julius,如果 int print7() 示例函数返回 void?它无法创建 void 类型的类属性。
    • @user:如果print7返回void,这个例子没有问题。如果GETPARAM 返回 void 则有问题,但在这种情况下这个问题没有意义。
    • @user:一般来说(如果你只是想调用一些函数而忽略返回)那么最简单的方法就是(funcs_which_may_return_void(), ...);。它是逗号运算符上的 C++17 fold expression 并且保证了评估顺序。如果 C++17 不可用,则有以下解决方法:int dummy[] = {0, ((void)(funcs_which_may_return_void()), 1)...};。在这里,逗号运算符用于将每个返回“替换”为1。为了抑制编译器警告,您可能需要添加第二行 (void)dummy;。
    【解决方案3】:

    这是用于静态构建具有任意数量参数的任何函数的代码。它完全独立于 等任何编译器和 或 等库(使用printf 除外,但您可以安全地删除它们)。但由于可变参数模板需要。

    #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" );
    }
    

    运行它你会看到测试结果:

    $ 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)
    

    你可以用clang++运行它来查看生成的模板代码:

    $ 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);
        }
    // ...
    

    参考文献

    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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-01
      • 2019-08-25
      • 2016-12-01
      • 1970-01-01
      • 2016-10-05
      • 2018-05-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多