具有参数数目可变的函数成员的容器
Container with function member with variable number of arguments
我正在尝试用一种更好的方式来表示以下内容:
using InsBase0 = std::tuple<std::string, std::function<void()>>;
static const std::array<InsBase0, 1> ins0_bases = {{
{"NOP", 0x0},
}};
using InsBase1 = std::tuple<std::string, std::function<void(const std::string &)>>;
static const std::array<InsBase1, 7> ins1_bases = {{
{"INC", 0x0},
{"DEC", 0x0},
{"AND", 0x0},
{"OR", 0x0},
{"XOR", 0x0},
{"CP", 0x0},
{"SUB", 0x0},
}};
using InsBase2 = std::tuple<std::string, std::function<void(const std::string &, const std::string&)>>;
static const std::array<InsBase2, 6> ins_bases = {{
{"LD", 0x0},
{"ADD", 0x0},
{"ADC", 0x0},
{"SBC", 0x0},
{"JP", 0x0},
{"JR", 0x0},
}};
(完全人为的例子,想象用函数代替0x0
,用map代替数组或用struct代替元组)
上下文是这是一个汇编程序,所以我需要将指令映射到函数。
在一个完美的世界中,我希望能够将所有的指令放入一个数组/容器中(使用额外的args
成员来表示函数所需的参数数量),但我很高兴不重复StructName0
的定义以及
两个元编程助手:
template<std::size_t I>
using index=std::integral_constant<std::size_t, I>;
template<class T> struct tag_t {constexpr tag_t(){};};
template<class T> tag_t<T> tag{};
template<std::size_t, class T>
using indexed_type = T;
现在为每个实参计数定义一个enum类型:
enum class zero_op:std::size_t { NOP };
enum class one_op:std::size_t { INC };
enum class two_op:std::size_t { ADD };
接下来,从类型到实参计数的映射:
constexpr index<0> args( tag_t<zero_op> ) { return {}; }
constexpr index<1> args( tag_t<one_op> ) { return {}; }
constexpr index<2> args( tag_t<two_op> ) { return {}; }
接受一个模板、一个计数和一个类型,并反复将该类型传递给模板:
template<template<class...>class Z, class T, class Indexes>
struct repeat;
template<template<class...>class Z, class T, std::size_t I>
struct repeat<Z, T, index<I>>:
repeat<Z, T, std::make_index_sequence<I>>
{};
template<template<class...>class Z, class T, std::size_t...Is>
struct repeat<Z, T, std::index_sequence<Is...>> {
using type=Z<indexed_type<Is, T>...>;
};
template<template<class...>class Z, class T, std::size_t N>
using repeat_t = typename repeat<Z, T, index<N>>::type;
我们使用它来构建std::function
签名:
template<class...Args>
using void_call = std::function<void(Args...)>;
template<std::size_t N, class T>
using nary_operation = repeat_t< void_call, T, N >;
和nary_operation< 3, std::string const& >
是std::function<void(std::string const&,std::string const&,std::string const&)>
。
我们用它来创建一个编译时多态表:
template<class...Es>
struct table {
template<class E>
using operation = nary_operation<args(tag<E>), std::string const&>;
template<class E>
using subtable = std::map< E, operation<E> >;
std::tuple< subtable<Es>... > tables;
template<class E>
operation<E> const& operator[]( E e ) {
return std::get< subtable<E> >( tables )[e];
}
};
之类的
如果您有一个table<zero_op, one_op, two_op> bob
的实例,您可以执行
bob[ zero_op::NOP ]();
或
bob[ zero_op::INC ]("foo");
或
bob[ zero_op::ADD ]("foo", "bar");
[]
中枚举的类型改变了返回的函数对象的类型。
上面可能有拼写错误。
但是,最终结果是类型安全的
我无法找到一种将所有操作存储在一个结构中并且仍然有编译时检查的方法。然而,可以在运行时检查传递的值的数量。
#include <iostream>
#include <functional>
#include <string>
#include <unordered_map>
class operation
{
using op0_funcptr = void(*)();
using op1_funcptr = void(*)(const std::string&);
using op2_funcptr = void(*)(const std::string&, const std::string&);
using op0_func = std::function<void()>;
using op1_func = std::function<void(const std::string&)>;
using op2_func = std::function<void(const std::string&, const std::string&)>;
std::tuple<
op0_func,
op1_func,
op2_func> m_functions;
public:
operation() :m_functions(op0_func(), op1_func(), op2_func()) {}
operation(const op0_func& op) :m_functions(op, op1_func(), op2_func()) {}
operation(const op0_funcptr& op) :m_functions(op, op1_func(), op2_func()) {}
operation(const op1_func& op) :m_functions(op0_func(), op, op2_func()) {}
operation(const op1_funcptr& op) :m_functions(op0_func(), op, op2_func()) {}
operation(const op2_func& op) :m_functions(op0_func(), op1_func(), op) {}
operation(const op2_funcptr& op) :m_functions(op0_func(), op1_func(), op) {}
operation(const operation& other) = default;
operation(operation&& other) = default;
void operator()() { std::get<op0_func>(m_functions)(); }
void operator()(const std::string& p1) { std::get<op1_func>(m_functions)(p1); }
void operator()(const std::string& p1, const std::string& p2) { std::get<op2_func>(m_functions)(p1, p2); }
};
void nop()
{
std::cout << "NOP" << std::endl;
}
void inc(const std::string& p1)
{
std::cout << "INC(" << p1 << ")" << std::endl;
}
void add(const std::string& p1, const std::string& p2)
{
std::cout << "ADD(" << p1 << ", " << p2 << ")" << std::endl;
}
std::unordered_map<std::string, operation> operations{ {
{ "NOP", nop },
{ "INC", inc },
{ "ADD", add }
} };
int main(int argc, char** argv)
{
operations["NOP"]();
operations["INC"]("R1");
operations["ADD"]("R2", "R3");
operations["ADD"]("R2"); //Throws std::bad_function_call
}
这不是最好的解决方案,但它有效。
如果你想让访问更快,你也可以试着把下面的部分改成这样:
enum class OP : size_t
{
NOP,
INC,
ADD,
NUM_OPS
};
std::array<operation, (size_t)OP::NUM_OPS> operations{ nop ,inc, add };
int main(int argc, char** argv)
{
operations[(size_t)OP::NOP]();
operations[(size_t)OP::INC]("R1");
operations[(size_t)OP::ADD]("R2", "R3");
//operations[(size_t)OP::ADD]("R2"); //Throws std::bad_function_call
}
我建议使用单个std::map
,其中关键是函数的名称(NOP
, AND
, ADD
等)。
使用继承,一个简单的基类,一个std::function
包装器…
我想不是很优雅,但是…
#include <map>
#include <memory>
#include <iostream>
#include <functional>
struct funBase
{
// defined only to permit the dynamic_cast
virtual void unused () const {};
};
template <typename R, typename ... Args>
struct funWrap : public funBase
{
std::function<R(Args...)> f;
funWrap (R(*f0)(Args...)) : f { f0 }
{ }
};
template <typename R, typename ... Args>
std::unique_ptr<funBase> makeUFB (R(*f)(Args...))
{ return std::unique_ptr<funBase>(new funWrap<R, Args...>(f)); }
template <typename F, typename T, bool = std::is_convertible<F, T>::value>
struct getConv;
template <typename F, typename T>
struct getConv<F, T, true>
{ using type = T; };
template <typename F, typename T>
struct getConv<F, T, false>
{ };
template <typename ... Args>
void callF (std::unique_ptr<funBase> const & fb, Args ... args)
{
using derType = funWrap<void,
typename getConv<Args, std::string>::type const & ...>;
derType * pdt { dynamic_cast<derType *>(fb.get()) };
if ( nullptr == pdt )
std::cout << "call(): error in conversion" << std::endl;
else
pdt->f(args...);
}
void fNop ()
{ std::cout << "NOP!" << std::endl; }
void fAnd (std::string const & s)
{ std::cout << "AND! [" << s << ']' << std::endl; }
void fAdd (std::string const & s1, std::string const & s2)
{ std::cout << "ADD! [" << s1 << "] [" << s2 << ']' << std::endl; }
int main()
{
std::map<std::string, std::unique_ptr<funBase>> fm;
fm.emplace("NOP", makeUFB(fNop));
fm.emplace("AND", makeUFB(fAnd));
fm.emplace("ADD", makeUFB(fAdd));
callF(fm["NOP"]); // print NOP!
callF(fm["AND"], "arg"); // print AND! [arg]
callF(fm["ADD"], "arg1", "arg2"); // print ADD! [arg1] [arg2]
callF(fm["ADD"], "arg1"); // print call(): error in conversion
//callF(fm["ADD"], "arg1", 12); // compilation error
return 0;
}
注。
您可以使用std::vector<std::string>
代替std::string
参数,这样您就可以存储多个参数。
与以下内容有关:
using function_t = std::function<void(const std::vector<std::string>&)>;
static const std::unordered_map<std::string, function_t> instructions =
{
{"INC", somefunc},
...
};
然后调用适当的指令:
std::vector<std::string> arguments = { "zob", "zeb" };
auto result = instructions["INC"](arguments);
编辑:
以下是我将如何做的其余部分,以证明它没有那么长:
/**
* Your instruction type. Contains its name,
* the function to call, and the number of args required
*/
struct Instruction {
using function_t = std::function<void(std::vector<std::string>)>;
std::string name;
function_t function;
std::size_t numargs;
Instruction(const std::string& name = "undefined", const function_t& function = function_t(), std::size_t numargs = 0)
: name(name)
, function(function)
, numargs(numargs) {}
}
/**
* Your instruction set. It contains the instructions you want to register in.
* You can call the instructions safely through this
*/
struct InstructionSet {
std::unordered_map<std::string, Instruction> instructions;
void callInstruction(const std::string& inst_name, const std::vector<std::string>& arguments) {
if (!instructions.count(inst_name))
return; // no instruction named "inst_name", return or throw something relevant
auto instruction = instructions[inst_name];
if (instruction.numargs != arguments.size())
return; // too many / not enough parameters, return or throw something relevant
instruction.function(arguments);
}
void registerInstruction(const Instruction& instruction) {
instructions[instruction.name] = instruction;
}
};
int main() {
InstructionSet instruction_set;
instruction_set.registerInstruction(Instruction(
"INC",
[](const std::vector<std::string>& arguments) {
bake_cookies_plz(arguments);
},
2)
);
instruction_set.callInstruction("INC", { "1", "2" });
return 0;
}
- 注1:在本例中,InstructionSet负责检查传递的参数数量,但函数可以自己检查。如果有变量参数count 的可能性,我会这样做注2:注册部分在lambdas中不是很优雅,但是可以很快地编写
- 注3:如果你想让你的参数有更多的类型安全,去查看max66答案,了解如何在这种情况下掌握模板
- 如何使用指针传递给函数的数组中对象的函数成员
- c++构造函数成员初始化:传递参数
- 创建 std::函数,它返回具有函数成员值的变量.分段错误
- 如何在C++通过公共函数访问私有函数成员?
- 解释了构造函数成员初始化列表
- 调用std::函数成员时内存损坏
- 是否可以为模板类的模板函数成员设置别名?
- 捕获 lambda 函数C++成员变量
- 构造函数成员初始值设定项跨成员列出,安全吗?
- 获取与在模板参数中传递的函数成员类型相同的类
- 如何从公共函数成员访问地图私有成员
- C 构造函数成员分配优化
- 使用命名空间进行函数成员定义
- 函数成员作为 CUDA 内核的参数
- 模板基类函数成员的别名
- 函数成员中用于void和继承的enable_if
- 头文件中是否定义了一个很长的Class函数成员
- 类内/构造函数成员初始化
- 使用指向部分专用函数成员的指针自动填充向量
- 指向函数成员的指针