我正在努力实现与RapidCheck相同的效果:无论其参数如何,都可以调用任何 Callable 。
这是RapidCheck的一个例子:
#include <rapidcheck.h>
int main() {
rc::check("Addition is commutative.",
[](int a, int b) {
RC_ASSERT(a + b == b + a);
});
return 0;
}
uint8_t
数组。 (参见下面的例子。)到目前为止,我发表了一些评论(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;
}
答案 0 :(得分:0)
您可以使用variadic templates。
如果要在运行时调用具有任意参数的任意签名的任意函数,则应考虑使用libffi(一个知道您的ABI和调用约定的外部函数接口库)。
答案 1 :(得分:0)
我找到了使用callable.hpp的解决方案。仍然欢迎不依赖外部图书馆的答案!
相关的新增内容如下:
constexpr size_t pos = sizeof...(args);
typedef typename callable_traits<F>::template argument_type<pos> T;
auto val = Data->pop<T>();
完整示例:
#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;
}
打印:
String: hello Ada
Integers: 7
Mixed: 42, 3.14159, Pi!
Nothing to do!