【问题标题】:How to store and call a variable argument function and a vector of values?如何存储和调用变量参数函数和值向量?
【发布时间】:2020-11-16 11:46:28
【问题描述】:

在 Python 中,可以存储具有不同数量参数的函数指针并将参数存储在列表中,然后解包列表并调用该函数,如下所示:

def Func(x1, x2):
   return x1+x2
ArgList = [1.2, 3.22]
MyClass.Store(Func)
MyClass.FuncPtr(*ArgList)

在 c++ 中可以做类似的事情吗?

例如,将具有可变数量输入和值的函数存储在std::vector 中并通过该向量调用函数指针?

我不想将参数列表定义为向量。

【问题讨论】:

  • 不像在 Python 中那样,在 Python 中,它是一个列表和一个字典,它们是实际的参数。你可以创建一个可变参数对象,但需要大量的脚手架。
  • 在 C++17 中,您可以执行类似 this 的操作。但我不确定这就是你想要的。
  • I don’t want to define argument list as vector. - 为什么不呢?
  • @Hesam,std::apply 本身适用于任何可以使用给定数量的参数调用的东西,但std::function 应该在编译时知道参数的数量。
  • Python 允许您在调用函数时不匹配参数和参数。默认情况下,C++ 会更早地阻止你,而且要复制 Python 的灵活性非常困难。如果我使用您的 API,我不希望灵活性出错

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


【解决方案1】:

在 c++ 中可以做类似的事情吗?

如果您在编译时知道 arguments(types),当然可以!

例如,类似于/类似于 @Evg 建议的内容,将Func 设为variadic-generic-lambda,并在std::apply 的支持下,(对于未来的读者)你可以做到。

(See a demo)

#include <iostream>
#include <utility> // std::forward
#include <tuple>   // std::apply

constexpr auto func_lambda = [](auto&&... args) {
     return (std::forward<decltype(args)>(args) + ...); // fold expression
};

int main() 
{
   std::cout << std::apply(func_lambda, std::make_tuple(1, 2, 3));  // prints 6!
   return 0;
}

现在根据您的 演示代码,您可以将上述想法打包到class-template 中。 在这里,您可以避免使用std::function,因为Func 将是您传递的lambda,并且可以存储为compiler deduced lambda unspecified type

(See Demo Online)

#include <iostream>
#include <utility> // std::forward
#include <tuple>   //std::apply, std::make_tuple

// `Func` as variadic-generic-lambda
constexpr auto func_lambda = [](auto&&... args) noexcept {
   return (std::forward<decltype(args)>(args) + ...);
};

template<typename Func>
class MyClass final
{
   Func mFunction;
public:
   explicit MyClass(Func func)  // Store: pass the lambda here!
      : mFunction{ func }
   {}

   template<typename... Args>  // to execute the `Func` with `Args`! 
   constexpr auto execute(Args&&... args) const noexcept
   { 
      return std::apply(mFunction, std::make_tuple(std::forward<Args>(args)...));
   }
};

int main() 
{
   MyClass myClass{ func_lambda };         // MyClass.Store(Func)
   std::cout << myClass.execute(1.2, 2.82, 3);  // MyClass.FuncPtr(*ArgList)
   return 0;
}

输出

7.02

【讨论】:

  • 谢谢,这与我需要的很接近,但我希望 MyClass.execute 采用双值向量。
  • @Hesam 听起来像是一些额外的工作。为了使用上面的代码,这有点困难/甚至是不可能的。但是,您可以尝试将std::vector 转换为std::tuple。以下帖子可能会有所帮助:convert vector to tuple
  • @Hesam。向量是运行时大小的。您需要使用编译时大小的东西(如 std::tuple)。
【解决方案2】:

不幸的是,在运行时不可能以完全动态的方式调用传递任意值的任意函数。至少 C++ 代码的某些部分必须编译对具有完全相同签名的函数的静态调用。

问题在于,在 C++ 中,参数传递只能由编译器处理,甚至在运行时只执行 inspection 的可变参数函数(例如 printf)通常需要不同且更简单的调用约定。

一个技术问题是现代 ABI 极其复杂且不对称(例如,一些参数在寄存器中,一些在特殊 FPU 寄存器中,一些在堆栈中)并且只在编译时处理。

您可以做的是“包装”您希望能够在函数中调用的函数,例如接受泛型值的std::vector,然后调用将这些参数转换为正确类型的函数,例如像

Value myabs(const std::vector<Value>& args) {
    if (args.size() != 1) throw std::runtime_error("Bad arity");
    return Value(fabs(args[0].to<double>()));
}

那么动态调用这些包装器很容易,因为它们的签名都是相同的。

不幸的是,必须为您需要能够调用的每个函数编写(或生成)这些包装器,以便编译器实际上可以生成调用常规 C++ 函数所需的代码。

我不是模板专家,但可以使用 C++ 元编程来生成包装器,而不是手动编写每个包装器或编写外部代码生成器。

这是一个非常基本的例子:

typedef Value (*FWrapper)(const std::vector<Value>&);

template<typename RV, typename T1, typename T2, RV (*f)(T1, T2)>
Value wrapper2(const std::vector<Value>& args) {
    if (args.size() != 2) throw std::runtime_error("Bad arity");
    return f(args[0].to<T1>(), args[1].to<T2>());
}

例如包装double atan2(double, double)你可以简单地写:

FWrapper myatan2 = &wrapper2<double, double, double, &atan2>;

我尝试了一些尝试,但没有成功,以避免显式传递返回值类型以及参数的数量和类型,而是从传递的函数指针中提取它们,但对于最近的 C++,这也是可能的,甚至是可行的。

这个想法基本上是在进行模板实例化时调用编译器以按需构建特定的函数调用。

【讨论】:

    【解决方案3】:

    不完全相同,但在 C++ 中,您可以将组合函数应用于一系列值以产生单个结果。 std::accumulate 可以在这里为您提供帮助。但是,它适用于给定的范围,而不是可变参数。

    #include <numeric>
    #include <vector>
    #include <functional>
    #include <iostream>
    
    class MyClass
    {
    public:
        template<typename F, typename T>
        T Execute(F &&fn, const T initialValue, const std::vector<T> &values)
        {
            auto result = std::accumulate(values.begin(), values.end(),
                initialValue, std::forward<F>(fn));
            return result;
        }
    
    };
    
    int main()
    {
        MyClass cls;
        std::vector<double> values = { 1.2, 3.22 };
        auto sum = cls.Execute(std::plus<double>{}, 0.0, values);
        std::cout << "Sum = " << sum << std::endl;
    
        auto product = cls.Execute(std::multiplies<double>{}, 1.0, values);
        std::cout << "Product = " << product << std::endl;
    
        std::vector<int> values2 = { 10, 20 };
        auto subtractFrom200 = cls.Execute(std::minus<int>{}, 200, values2);
        std::cout << "Subtract from 200 = " << subtractFrom200 << std::endl;
    
        std::vector<std::string> mystrings = {"Hello", " ", " world", ". ", "Bye!"};
        auto concatenate = cls.Execute([](std::string a, std::string b){ return a + b ;}, std::string(), mystrings);
        std::cout << "Concatenation = " << concatenate << std::endl;
    
        std::vector<double> values3 = {100, 98, 3.5, 50};
        auto findMin = [](double a, double b){ return std::min(a, b); };
        auto lowest = cls.Execute(findMin, values3.front(), values3);
        std::cout << "Min = " << lowest << std::endl;
    }
    

    Demo

    注意:std::accumulate 的第四个参数是可选的,如果省略,它将返回值的总和。但是,如果您想做其他事情,您可以提供自己的二进制函数,例如乘法或减法。

    【讨论】:

    • 谢谢,这是一个不错的方法,但问题是它似乎只对 sum 有效,而函数可能非常通用。
    • @Hesam 它不仅适用于总和。请参阅我上面更新的示例以及演示。我在这里使用了模板,以便您可以提供任何类型的二进制函数。 std::accumulate 类似于 Python 中的 reduce
    猜你喜欢
    • 2021-12-30
    • 2019-08-03
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 2020-08-21
    • 2019-10-31
    • 2015-03-24
    相关资源
    最近更新 更多