C ++调用具有未知类型参数的函数?

时间:2021-07-21 03:10:55

I am trying to achieve the same as RapidCheck: call any Callable no matter its arguments.

我试图实现与RapidCheck相同的功能:无论其参数如何调用任何Callable。

Here is an example from RapidCheck:

以下是RapidCheck的示例:

#include <rapidcheck.h>

int main() {
  rc::check("Addition is commutative.",
            [](int a, int b) {
              RC_ASSERT(a + b == b + a);
            });

  return 0;
}
  • Unlike RapidCheck, which uses random data to call the Callable, I would like to use a source of data. In particular, I am casting a uint8_t array to whatever type I need. (See the example below.)
  • 与使用随机数据调用Callable的RapidCheck不同,我想使用数据源。特别是,我正在将uint8_t数组转换为我需要的任何类型。 (见下面的例子。)

  • I am okay with using C++17 but would prefer C++11. I currently only have a C++17 example.
  • 我可以使用C ++ 17,但更喜欢C ++ 11。我目前只有一个C ++ 17示例。

  • My example below works for an arbitrary number of arguments to a Callable but not for arbitrary types of argument. And certainly not for mixed types of arguments.
  • 下面的示例适用于Callable的任意数量的参数,但不适用于任意类型的参数。当然也不适用于混合类型的论点。

  • I am doing this, so that I can use the awesome API of RapidCheck with libFuzzer from LLVM. Similar to my previous approach here (Example).
  • 我这样做,所以我可以使用RapidCheck的强大API和LLVM的libFuzzer。与我之前的方法类似(示例)。

What I have so far with some comments (online):

到目前为止,我有一些评论(在线):

// Compiles with Clang(trunk)/GCC(7.2) using -std=c++17!

#include <cassert>
#include <functional>
#include <iostream>
#include <mutex>
#include <string>
#include <type_traits>

/** Provides access to a uint8_t array as specific types.
*
* Fulfills thus the LLVMFuzzerTestOneInput-interface, which uses
* (uint8_t *Data, size_t Size) as input.
*/
class RawQueue {
public:
  RawQueue(uint8_t *Data, size_t Size)
      : data_(Data), size_(Size / sizeof(uint8_t)), index_(0){};

  /** Takes one element of type T from queue.
  *
  * Throws if empty.
  *
  * NOTE: Little endianess means that uint8_t {1, 0, 0, 0} == int {1}.
  */
  template <typename T> T pop() {
    assert(data_);
    std::scoped_lock<std::mutex> lock(data_mutex_);

    const size_t new_index = index_ + sizeof(T) / sizeof(uint8_t);
    if (new_index > size_) {
      std::runtime_error(
          "Queue depleted!"); // TODO: Thou shall not use plain runtime_error!
    }

    const T val = *reinterpret_cast<const T *>(&(data_[index_]));
    index_ = new_index;

    return val;
  }

private:
  const uint8_t *data_; ///< Warning: Ownership resides outside of RawQueue.
  std::mutex data_mutex_;
  const size_t size_;
  size_t index_;
};

template <> std::string RawQueue::pop<std::string>() {
  return std::string("Left-out for brevity.");
};

template <typename T, typename F, typename... Args>
decltype(auto) call(RawQueue *Data, F &&f, Args &&... args) {
  if constexpr (std::is_invocable<F, Args...>::value) {
    return std::invoke(f, args...);
  } else {
    assert(Data);
    // Is there a way to deduce T automatically and for each argument
    // independently?
    auto val = Data->pop<T>();
    return call<T>(Data, f, val, args...);
  }
}

int adder(int a, int b, int c) { return a + b + c; }
std::string greeter(const std::string &name) { return "Hello, " + name + "!"; }

int mixed_arguments(int i, float f, const std::string &s) { return 42; }

int main() {
  constexpr size_t Size = 16;
  uint8_t Data[Size] = {1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0};
  RawQueue data(Data, Size);

  auto res_int = call<int>(&data, adder);
  std::cout << "Integer result: " << res_int << std::endl;

  auto res = call<std::string>(&data, greeter);
  std::cout << "String result: " << res << std::endl;

  // Impossible with current approach:
  // std::cout << "Mixed-types: " << call(&data, mixed_arguments) << std::endl;

  return 0;
}

2 个解决方案

#1


0  

You could use variadic templates.

您可以使用可变参数模板。

If you want to call, at runtime, an arbitrary function of arbitrary signature with arbitrary arguments, you should consider using libffi (a foreign function interface library which knows your ABI and calling conventions).

如果要在运行时调用具有任意参数的任意签名的任意函数,则应考虑使用libffi(一个知道您的ABI和调用约定的外部函数接口库)。

#2


0  

I found a solution using callable.hpp. Answers which do not rely on an external library are still welcome!

我找到了一个使用callable.hpp的解决方案。仍然欢迎不依赖外部库的答案!

The relevant new addition is this:

相关的新增内容如下:

constexpr size_t pos = sizeof...(args);
typedef typename callable_traits<F>::template argument_type<pos> T;
auto val = Data->pop<T>();

Complete Example:

#include <cassert>
#include <functional>
#include <iostream>
#include <mutex>
#include <string>
#include <type_traits>

#include "external/callable/callable.hpp"

class RawQueue {
public:
  RawQueue(uint8_t *Data, size_t Size)
      : data_(Data), size_(Size / sizeof(uint8_t)), index_(0){};

  /** Takes one element of type T from queue.
  *
  * Throws if empty.
  *
  * NOTE: Little endianess means that uint8_t {1, 0, 0, 0} == int {1}.
  */
  template <typename T> T pop() {
    assert(data_);
    std::scoped_lock<std::mutex> lock(data_mutex_);

    const size_t new_index = index_ + sizeof(T) / sizeof(uint8_t);
    if (new_index > size_) {
      // TODO: Thou shall not use plain runtime_error!
      std::runtime_error("Queue depleted!");
    }

    const T val = *reinterpret_cast<const T *>(&(data_[index_]));
    index_ = new_index;

    return val;
  }

private:
  const uint8_t *data_; ///< Warning: Ownership resides outside of RawQueue.
  std::mutex data_mutex_;
  const size_t size_;
  size_t index_;
};

template <> std::string RawQueue::pop<std::string>() {
  std::scoped_lock<std::mutex> lock(data_mutex_);
  assert(data_);
  assert(index_ < size_);
  size_t string_length = data_[index_]; // Up-to 255 ought to be enough.
  const size_t new_index =
      index_ + string_length + 1; // +1 b/c first value is length of string.

  if (new_index > size_) {
    // TODO: Thou shall not use plain runtime_error!
    std::runtime_error("Queue depleted!");
  }

  const std::string val(reinterpret_cast<const char *>(&(data_[index_ + 1])),
                        string_length);
  index_ = new_index;
  return val;
};

template <typename F, typename... Args>
decltype(auto) call(RawQueue *Data, F &&f, Args &&... args) {
  if constexpr (std::is_invocable<F, Args...>::value) {
    return std::invoke(f, args...);
  } else {
    assert(Data);
    constexpr size_t n_already = sizeof...(args);
    constexpr size_t n_needed = callable_traits<F>::argc;
    static_assert(n_needed >= n_already, "Too many arguments!");
    constexpr size_t pos = n_already;
    typedef typename callable_traits<F>::template argument_type<pos> T;
    auto val = Data->pop<T>();
    return call(Data, f, args..., val);
  }
}

int adder(int a, int b, int c) { return a + b + c; }

std::string greeter(std::string a) { return "hello " + a; };

void mixed(int i, float f, std::string s) {
  std::cout << "Mixed: " << i << ", " << f << ", " << s << std::endl;
}

int main() {
  constexpr size_t Size = 28;
  // clang-format off
  uint8_t Data[Size] = {
      3, 'A', 'd', 'a',
      1, 0, 0, 0,
      2, 0, 0, 0,
      4, 0, 0, 0,
      42, 0, 0, 0,
      0xDA, 0x0F, 0x49, 0x40, // 3.141...
      3, 'P', 'i', '!'};
  // clang-format on
  RawQueue data(Data, Size);

  std::cout << "String: " << call(&data, greeter) << std::endl;
  std::cout << "Integers: " << call(&data, adder) << std::endl;
  call(&data, mixed);
  call(&data, []() { std::cout << "Nothing to do!" << std::endl; });

  return 0;
}

Prints:

String: hello Ada
Integers: 7
Mixed: 42, 3.14159, Pi!
Nothing to do!

#1


0  

You could use variadic templates.

您可以使用可变参数模板。

If you want to call, at runtime, an arbitrary function of arbitrary signature with arbitrary arguments, you should consider using libffi (a foreign function interface library which knows your ABI and calling conventions).

如果要在运行时调用具有任意参数的任意签名的任意函数,则应考虑使用libffi(一个知道您的ABI和调用约定的外部函数接口库)。

#2


0  

I found a solution using callable.hpp. Answers which do not rely on an external library are still welcome!

我找到了一个使用callable.hpp的解决方案。仍然欢迎不依赖外部库的答案!

The relevant new addition is this:

相关的新增内容如下:

constexpr size_t pos = sizeof...(args);
typedef typename callable_traits<F>::template argument_type<pos> T;
auto val = Data->pop<T>();

Complete Example:

#include <cassert>
#include <functional>
#include <iostream>
#include <mutex>
#include <string>
#include <type_traits>

#include "external/callable/callable.hpp"

class RawQueue {
public:
  RawQueue(uint8_t *Data, size_t Size)
      : data_(Data), size_(Size / sizeof(uint8_t)), index_(0){};

  /** Takes one element of type T from queue.
  *
  * Throws if empty.
  *
  * NOTE: Little endianess means that uint8_t {1, 0, 0, 0} == int {1}.
  */
  template <typename T> T pop() {
    assert(data_);
    std::scoped_lock<std::mutex> lock(data_mutex_);

    const size_t new_index = index_ + sizeof(T) / sizeof(uint8_t);
    if (new_index > size_) {
      // TODO: Thou shall not use plain runtime_error!
      std::runtime_error("Queue depleted!");
    }

    const T val = *reinterpret_cast<const T *>(&(data_[index_]));
    index_ = new_index;

    return val;
  }

private:
  const uint8_t *data_; ///< Warning: Ownership resides outside of RawQueue.
  std::mutex data_mutex_;
  const size_t size_;
  size_t index_;
};

template <> std::string RawQueue::pop<std::string>() {
  std::scoped_lock<std::mutex> lock(data_mutex_);
  assert(data_);
  assert(index_ < size_);
  size_t string_length = data_[index_]; // Up-to 255 ought to be enough.
  const size_t new_index =
      index_ + string_length + 1; // +1 b/c first value is length of string.

  if (new_index > size_) {
    // TODO: Thou shall not use plain runtime_error!
    std::runtime_error("Queue depleted!");
  }

  const std::string val(reinterpret_cast<const char *>(&(data_[index_ + 1])),
                        string_length);
  index_ = new_index;
  return val;
};

template <typename F, typename... Args>
decltype(auto) call(RawQueue *Data, F &&f, Args &&... args) {
  if constexpr (std::is_invocable<F, Args...>::value) {
    return std::invoke(f, args...);
  } else {
    assert(Data);
    constexpr size_t n_already = sizeof...(args);
    constexpr size_t n_needed = callable_traits<F>::argc;
    static_assert(n_needed >= n_already, "Too many arguments!");
    constexpr size_t pos = n_already;
    typedef typename callable_traits<F>::template argument_type<pos> T;
    auto val = Data->pop<T>();
    return call(Data, f, args..., val);
  }
}

int adder(int a, int b, int c) { return a + b + c; }

std::string greeter(std::string a) { return "hello " + a; };

void mixed(int i, float f, std::string s) {
  std::cout << "Mixed: " << i << ", " << f << ", " << s << std::endl;
}

int main() {
  constexpr size_t Size = 28;
  // clang-format off
  uint8_t Data[Size] = {
      3, 'A', 'd', 'a',
      1, 0, 0, 0,
      2, 0, 0, 0,
      4, 0, 0, 0,
      42, 0, 0, 0,
      0xDA, 0x0F, 0x49, 0x40, // 3.141...
      3, 'P', 'i', '!'};
  // clang-format on
  RawQueue data(Data, Size);

  std::cout << "String: " << call(&data, greeter) << std::endl;
  std::cout << "Integers: " << call(&data, adder) << std::endl;
  call(&data, mixed);
  call(&data, []() { std::cout << "Nothing to do!" << std::endl; });

  return 0;
}

Prints:

String: hello Ada
Integers: 7
Mixed: 42, 3.14159, Pi!
Nothing to do!