2016-11-07 65 views
1

我试图想出代表下面的一个更好的方式可变数量的函数成员:集装箱用的参数

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成员来表示函数所需的参数数量),但我会很高兴而不是与StructName0一起复制定义

+0

我会使用枚举而不是字符串的操作名称。当然,这需要在解析时在两者之间进行转换,但打字错误的可能性会降低。此外,你的查找可能会更快。 –

+0

一种可能性,但我必须在某个点上从字符串转换... – LordAro

+0

你能发布一个你试图用这个函数调用的函数的例子吗? –

回答

1

您可以使用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:在本例中,指令集架构是负责检查的传递的参数数目,但功能可以自己做。我会这样做,如果有可能参数的可能性
  • 注2:注册部分是不是很优雅与lambda,但它的快速写
  • 注3:如果你想要更多的类型安全的参数,去检查max66答案,以了解如何在这种情况下掌握模板
+0

可能。问题在于没有好方法可以说函数传递了错误的参数数 – LordAro

+0

好的,所以你可以创建一个包含'function_t'和一些参数的结构。 该指令集本身将被封装在一个类中,其中包含一些方法'callInstruction(arguments)',用于检查指令[name] .argcount == arguments.size()' – brainsandwich

+0

看起来像很多样板。 – LordAro

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 
} 
+0

我目前正在朝这个方向努力,只是因为它是“更干净”的解决方案(纯粹是IMO),因为它没有魔法元编程,我没有正确理解:)我会接受假设我可以使它与我的工作代码 – LordAro

+0

我努力让操作映射工作 - 错误:没有匹配的构造函数来初始化'std :: unordered_map ''是否有我失踪的东西? (“构造函数不可行:需要x个参数,但提供了1个参数”) – LordAro

+0

@LordAro你有一个默认的构造函数吗? –

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。元编程佣工

+0

将'makeUFB'和'callF'滚动到构造函数和运算符中有多有效?似乎它会比额外的功能更干净。相同的虚拟未使用 - 可以是一个构造函数? – LordAro

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的intance,你可以做

bob[ zero_op::NOP ](); 

bob[ zero_op::INC ]("foo"); 

bob[ zero_op::ADD ]("foo", "bar"); 

的类型[]枚举变化的类型函数对象返回。

上面可能有错别字。

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

+0

我仍然需要以某种方式将op(“NOP”)的字符串表示形式变为枚举形式 - 关于这样做的想法(或者将它转换为此)? – LordAro

+0

@LordAro将字符串映射到枚举类型的变体,使用类型安全访问然后解析参数。或者将它从字符串直接映射到'std :: function's的缓存;最后一个类型可以通过我的'nary_operation'和一些额外的元编程来生成。 – Yakk

+0

我并没有真正的变体,因为没有C++ 17或boost,你能否展示后一个选项的例子? (或其他) – LordAro