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

时间:2016-11-07 15:11:04

标签: c++ function c++14

我试图想出一个更好的方式来代表以下内容:

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的函数和更像地图而不是数组或结构而不是元组的东西)

上下文是这是一个汇编程序,所以我需要将指令映射到函数。

在一个完美的世界中,我希望能够将所有指令放入一个数组/容器中(使用额外的args成员来表示该函数所需的args数量),但是我很高兴不会使用StructName0重复定义

4 个答案:

答案 0 :(得分:2)

两位元编程助手:

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 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");

[]中枚举的类型会更改返回的函数对象的类型。

以上可能有拼写错误。

但是,最终结果是类型安全的。

答案 1 :(得分:1)

您可以使用std::string代替函数的std::vector<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负责检查传递的参数数量,但函数可以自行完成。如果存在可变参数计数
  • ,我会这样做
  • 注2:使用lambdas注册部分不是很优雅,但写得很快
  • 注3:如果您想为参数提供更多类型安全性,请查看max66 answer以了解在这种情况下如何掌握模板

答案 2 :(得分:1)

我无法找到一种方法将所有操作存储在一个结构中,并且仍然有编译时间检查。但是可以在运行时检查传递的值的数量。

#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
}

答案 3 :(得分:1)

我建议使用单std::map,其中键是函数的名称(NOPANDADD等。)

使用继承,一个简单的基类,一个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;
 }

P.s。:也适用于C ++ 11。