具有参数数目可变的函数成员的容器

Container with function member with variable number of arguments

本文关键字:函数 成员 参数 数数      更新时间:2023-10-16

我正在尝试用一种更好的方式来表示以下内容:

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答案,了解如何在这种情况下掌握模板