2

C++ で拡張可能な小さなスクリプト インタープリターを作成しようとしています。その目的のために、関数ハンドラがディスパッチ テーブルに挿入されます。私の質問を簡単にするために、handlertype は次のように定義されます (実際のコードでは、これには引数リストと戻り型のパラメーターが含まれます)。

// Handler type
using function_type = void(int);

現時点では、ディスパッチ テーブルは単純な順序付けされていないマップであり、オーバーロードを実装するためのキーとして (一種の) マングルされた名前があります。

// Dispatch table
std::unordered_map<std::string, function_type*> fn_handlers;

メソッドはこのテーブルに直接追加されます。たとえば、タイプの 2 つの引数を取る単純なメソッドのようにint(operator+ii私の場合、これは管理された名前です):

fn_handlers["operator+ii"] = my_add_handler;

ただし、多くのハンドラー、特に基本的な数学に関連するハンドラーは、さまざまな引数を受け入れ、 と のすべての組み合わせintdouble有効であり、4 つのメソッドと 4 つのディスパッチ テーブル エントリが生成されます。したがって、テンプレートを使用してこれらのメソッドを実装することにしました。例を挙げると、これは基本的に次のようになります (これも単純化されています)。

template<class A, class B>
void my_add_handler(int /* simplified... */)
{
  // Something here A and B are needed
}

ディスパッチ テーブルは次のように埋められます。

fn_handlers["operator+ii"] = my_add_handler<int,int>;
fn_handlers["operator+di"] = my_add_handler<double,int>;
fn_handlers["operator+id"] = my_add_handler<int,double>;
fn_handlers["operator+dd"] = my_add_handler<double,double>;

まだ入力することがたくさんありますが、これで問題ありません。とにかく、テンプレート パラメーターとメソッド シグネチャ (マングルされた名前) の間には明らかに相関関係があるため、これを自動化して記述できるようにしました (パラメーター名のマンゲリングは handler_provider::add 内で行われます)。

handler_provider<int, int>::add<my_add_handler>("operator+");
handler_provider<double, int>::add<fn_add_handler>("operator+");
handler_provider<int, double>::add<fn_add_handler>("operator+");
handler_provider<double, double>::add<fn_add_handler>("operator+");

次に、最初に引数を取り、2番目のタイプのテンプレート引数として受け取ります(そのため、<int, int>パーツを2回入力する必要はありません)。

明確にするために。私は、次のmy_add_handlerようにテンプレートを明示的に特殊化することを認識しています。

handler_provider<int, int>::add<my_add_handler<int,int>>("test");

しかし、私が省略したいのはまさにこの重複です( twict <int,int>)。

ただし、最後の部分でエラーが発生し続けます。メソッドは次のhandler_provider::addように定義されます (上記のパラメーター名のマンゲリングは、ここでのポイントではなく、期待どおりに機能するため省略されています)。

template<class... Ts>
struct handler_provider
{
  // Overload for templates, such as 'test_handler'
  template<template<class...> class F>
  static void add(const std::string name)
  {
    handler_provider<Ts...>::add<F<Ts...>>(name);
  }

  // Overload for non-template (or already specialized) handlers (aka. function pointers)
  template<function_type F>
  static void add(const std::string name)
  {
    fn_handlers[name] = F;
  }
};

前述のように、最初のオーバーロードは、上記で説明した正確なケースを想定しています。以下のハンドラーは、非テンプレート関数と完全に特殊化された関数をインストールします。

ただし、これによりエラーが発生し、上記のような呼び出しからの内部テンプレートを推測できないことがわかります。コンパイラに何かを推測するように指示したとは思いませんでした。呼び出しでテンプレート引数を完全に特殊化しました (再び)。

handler_provider<int, int>::add<my_add_handler>("operator+");

外側の可変個引数テンプレートの引数class... Tsは明示的に名前が付けられ<int, int>、内側のテンプレート テンプレートの単純な引数は as という名前が付けられmy_add_handlerます。ただし、コンパイラはこれを無視しているようです(?)。これは私が得る出力です( gcc 5.4.0 using -std=c++14):

$ g++ -std=c++14 sci_e1.cpp -o sci
sci_e1.cpp: In function ‘int main()’:
sci_e1.cpp:45:55: error: no matching function for call to ‘handler_provider<int, int>::add(const char [5])’
  handler_provider<int, int>::add<my_add_handler>("operator+");
                                                             ^
sci_e1.cpp:17:15: note: candidate: template<template<class ...> class typedef F F> static void handler_provider<Ts>::add(std::__cxx11::string) [with F = F; Ts = {int, int}]
  static void add(const std::string name)
              ^
sci_e1.cpp:17:15: note:   template argument deduction/substitution failed:
sci_e1.cpp:24:15: note: candidate: template<void (* F)(int)> static void handler_provider<Ts>::add(std::__cxx11::string) [with void (* F)(int) = F; Ts = {int, int}]
  static void add(const std::string name)
              ^
sci_e1.cpp:24:15: note:   template argument deduction/substitution failed:
sci_e1.cpp:45:55: error: could not convert template argument ‘my_add_handler’ to ‘void (*)(int)’
  handler_provider<int, int>::add<my_add_handler>("operator+");
                                                             ^

2 番目のエラーが表示されます。これは完全に問題なく、テンプレート タイプのオーバーロード解決からこのオーバーロードを除外する必要があるため、問題にはなりません。最初のエラーは、私を狂わせるエラーです。

Clang (3.9.0) はもう少し正確です:

$ clang++ -std=c++14 sci_e1.cpp -o sci
sci_e1.cpp:45:3: error: no matching function for call to 'add'
  handler_provider<int, int>::add<my_add_handler>("test");
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sci_e1.cpp:17:15: note: candidate template ignored: invalid explicitly-specified argument for
      template parameter 'F'
  static void add(const std::string name)
              ^
sci_e1.cpp:24:15: note: candidate template ignored: invalid explicitly-specified argument for
      template parameter 'F'
  static void add(const std::string name)
              ^
1 error generated.

しかし、ここでどこが間違っているのかまだわかりません。私は何が欠けていますか?

ありがとう、

セバスチャン


より良いテストのために、完全な例を次に示します。

#include <unordered_map>
#include <string>
#include <iostream>

// Handler type
using function_type = void(int);

// Dispatch table
std::unordered_map<std::string, function_type*> fn_handlers;

// Handler provider (to install new handlers)
template<class... Ts>
struct handler_provider
{
  // Overload for templates, such as 'test_handler'
  template<template<class...> class F>
  static void add(const std::string name)
  {
    handler_provider<Ts...>::add<F<Ts...>>(name);
  }

  // Overload for non-template (or already specialized) handlers (aka. function pointers)
  template<function_type F>
  static void add(const std::string name)
  {
    fn_handlers[name] = F;
  }
};


template<class A, class B>
void test_handler(int v)
{
  // Something here A and B are needed
}

void other_handler(int v)
{
  // A handler without specialization
}

int main()
{
  // Install handlers
  handler_provider<int, int>::add<test_handler>("testii");
  handler_provider<double, int>::add<test_handler>("testdi");
  handler_provider<bool, bool, int>::add<other_handler>("otherbbi");

  // Dispatch
  fn_handlers["testii"](5); // Sould call test_handler<int, int>
  fn_handlers["testdi"](5); // Should call test_handler<double, int>

  fn_handlers["otherbbi"](5); // Should call other_handler
}
4

1 に答える 1

3

問題は次のとおりです。標準によると、[temp.arg.template]/1、

[a] テンプレートの template-argument template-parameter は、クラス テンプレートまたはエイリアス テンプレートの名前であり、id-expression として表されます。

したがって、テンプレートをインスタンス化することはできません

template<template<class...> class F>
static void add(const std::string name) {
    handler_provider<Ts...>::add<F<Ts...>>(name);
}

関数テンプレートを使用しtest_handlerます。

これを修正するにはtest_handler、代わりにテンプレート化されたファンクターを作成する必要があります。つまり、次のように変更します。

template<class A, class B>
struct test_handler {
    void operator()(int v) {
        // Something here A and B are needed
        std::cout << __PRETTY_FUNCTION__ << " called with v = " << v << std::endl;
    }
};

残念ながら、これはもはやタイプではないvoid(*)(int)ため、 に挿入することはできませんunordered_map。したがって、マップ内の要素を変更し、テンプレート化されたファンクターstd::function<function_type>のオーバーロードを調整する必要がありますadd

// Overload for templates, such as 'test_handler'
template<template<class...> class F>
static void add(const std::string name) {
    fn_handlers[name] = F<Ts...>{};
}

完全なコードは次のようになります。

#include <iostream>
#include <functional>
#include <string>
#include <unordered_map>

// Handler typ
using function_type = void(int);

// Dispatch table
std::unordered_map<std::string, std::function<function_type>> fn_handlers;

// Handler provider (to install new handlers)
template<class... Ts>
struct handler_provider {
    // Overload for templates, such as 'test_handler'
    template<template<class...> class F>
    static void add(const std::string name) {
        fn_handlers[name] = F<Ts...>{};
    }

    // Overload for non-template (or already specialized) handlers (aka. function pointers)
    template<function_type F>
    static void add(const std::string name) {
        fn_handlers[name] = F;
    }
};

template<class A, class B>
struct test_handler {
    void operator()(int v) {
        // Something here A and B are needed
        std::cout << __PRETTY_FUNCTION__ << " called with v = " << v << std::endl;
    }
};

void other_handler(int v) {
    // A handler without specialization
    std::cout << __PRETTY_FUNCTION__ << " called with v = " << v << std::endl;
}

int main() {
    // Install handlers
    handler_provider<int, int>::add<test_handler>("testii");
    handler_provider<double, int>::add<test_handler>("testdi");
    handler_provider<bool, bool, int>::add<other_handler>("otherbbi");

    // Dispatch
    fn_handlers["testii"](5); // Sould call test_handler<int, int>
    fn_handlers["testdi"](5); // Should call test_handler<double, int>

    fn_handlers["otherbbi"](5); // Should call other_handler
}

このcolirに見られるように、これはまさにあなたが望むことを行います。

オーバーヘッドのために使用したくない場合std::function(私のプラットフォームstd::functionではポインターに 8 バイトではなく 32 バイトを使用します)、ハンドラー用に独自の型消去構造体を作成することもできます。

于 2016-10-16T21:57:01.843 に答える