2016-11-07 71 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