web-dev-qa-db-ja.com

テンプレート引数タイプに基づいて関数を呼び出す

2つの「C」関数があります。

_void fooA(const char*);
void fooW(const wchar_t*);
_

次に、ラッパーテンプレート関数があります。

_template<typename _TChar>
void foo(const _TChar* str)
{
     // call fooA or fooB based on actual type of _TChar
     // std::conditional .. ?
         // fooA(str); 
         // fooW(str);
}
_

呼び出し元がfoo("Abc")を呼び出す場合、このテンプレート関数はコンパイル時にfooAを呼び出す必要があります。同様に、foo(L"Abc")fooWの最後の呼び出しを行う必要があります。

それ、どうやったら出来るの? _std::conditional_の使用を考えましたが、作成できませんでした。

fooAまたはfooBはC関数なので、オーバーロードできません。

18
Ajay

allあなたのwchar_tクラステンプレート内のバージョン。たとえば、次のようにoverloadsとそのchar対応する特殊化で対応します。

template<typename WideCharVersion> 
struct overloads
{
    void foo(wchar_t const * arg)
    {
       FooW(arg);
    }
    //more wchar_t functions
};

template<> 
struct overloads<std::false_type>
{
    void foo(char const * arg)
    {
       FooA(arg);
    }
    //more char functions
};

//a friendly alias!
template<typename T>
using is_wide_char = typename std::is_same<whar_t, T>::type;

そして、あなたはそれらを次のように使うことができます:

template<typename _TChar>
void foo(const _TChar* str)
{
    overloads<is_wide_char<_TChar>>::foo(str);
}

式SFINAEはそれを簡単にします!

他の方法は Expression SFINAE を使用することです。これはoverloadsのようなものを書く必要がなく、より少ないコードで同じ仕事をします:

template<typename _TChar>
void foo(const _TChar* str)
{
    invokeOne(fooA, fooW, str);
}

次に、invokeOneを次のように実装できます。

 template<typename F1, typename F2, typename ... Args>
 auto invokeOne(F1 f1, F2 f2, Args && ... args) -> decltype(f1(args...))
 {
     return f1(args...);
 }

 template<typename F1, typename F2, typename ... Args>
 auto invokeOne(F1 f1, F2 f2, Args && ... args) -> decltype(f2(args...))
 {
     return f2(args...);
 }

オンラインデモ をご覧ください。

このアプローチでは、overloadsクラステンプレートおよびにオーバーロードを追加して、特殊化する必要はありません。代わりに、それらを引数としてinvokeOneに渡すだけで、適切なオーバーロードが呼び出されます。

お役に立てば幸いです。

14
Nawaz

次に、別の関数をオーバーロードします。私はfooがより多くの作業を行い、テンプレートである必要があると想定しています。次に、そのように定義されたfoo_forward_callを呼び出します。

void foo_forward_call(char const* ptr) {
    FooA(ptr);
}

void foo_forward_call(wchar_t const* ptr) {
    FooW(ptr);
}

そして呼び出しサイトで:

template<typename _TChar>
void foo(const _TChar* str)
{
    foo_forward_call(str);
}

c ++ 1zではconstexpr ifを使用できますが、正直なところ、オーバーロードされたソリューションの方が読みやすいと思います。

template<typename _TChar>
void foo(const _TChar* str)
{
    if constexpr(std::is_same<_TChar, char>::value) {
        FooA(str);
    } else {
        FooW(str);
    }
}

または、Boost.Hanaの-​​ overload を使用できます。

template<typename _TChar>
void foo(const _TChar* str)
{
    hana::overload(fooA, fooW)(str);
}

デモ


ちなみに、プログラムではアンダースコアの大文字の名前を使用しないでください。これらは、あらゆる用途(マクロなど)の実装用に予約されており、厄介な名前の衝突につながる可能性があります。

6
krzaq

これは、テンプレートに関しては非常に奇妙なことのようです。代わりに通常のオーバーロードを使用することをお勧めします。

void foo(const char* p) { fooA(p); }
void foo(const wchar_t* p) { fooW(p); }

テンプレートの使用を主張する場合は、次のようにすることができます。

template <typename T>
void foo(const T* p)
{
    // Declare functions here so that calling fooW with const char*
    // and 'calling' fooA with const wchar_t* would not cause compile error.
    void fooA(const T*);
    void fooW(const T*);

    if (std::is_same<char, T>::value)
        fooA(p);
    else
        fooW(p);
}
6
lego

私は一般的に問題を解決するのが好きです。それで、ものをオーバーロードするメカニズムを設計しましょう。

_overload_t<...>_は、_..._の呼び出し可能オブジェクトのセットを受け取り、operator()の継承を介して、標準のオーバーロード解決を使用してそれらを選択するオブジェクトを生成します。

_template<class...Fs>
struct overload_t;
// the case where we have a function object:
template<class F>
struct overload_t<F>:F{
  overload_t(F f):F(std::move(f)){}
  using F::operator();
  // boilerplate to ensure these are enabled if possible:
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// we cannot inherit from a function pointer.  So
// store one, and write an `operator()` that forwards to it:
template<class R, class...Args>
struct overload_t<R(*)(Args...)>{
  using F=R(*)(Args...);
  F f;
  overload_t(F fin):f(fin){}
  R operator()(Args...args)const{
    return f(std::forward<Args>(args)...);
  }
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// the case where we have more than type to overload.
// recursively inherit from the one-arg and the rest-of-arg
// and using operator() to bring both of their () into equal standing:
template<class F0, class...Fs>
struct overload_t<F0,Fs...>:
  overload_t<F0>,
  overload_t<Fs...>
{
  using overload_t<F0>::operator();
  using overload_t<Fs...>::operator();
  overload_t(F0 f0, Fs...fs):
    overload_t<F0>(std::move(f0)),
    overload_t<Fs...>(std::move(fs)...)
  {}
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// a helper function to create an overload set without
// having to specify types.  Will be obsolete in C++17:
template<class...Fs>
overload_t<Fs...> overload(Fs...fs){ return {std::move(fs)...};}
_

次に、複数のオーバーロードである単一のオブジェクトを生成するには、次のようにします。

_overload(FooA,FooW)( str );
_

そして、strは通常のオーバーロード解決ルールに基づいてどちらかにディスパッチされます。これは他の場所でも役立ちます。そのため、書く価値があるので、使用時のコードは自己文書化されています。

実例 (すごい、最初は正しいと書きました!)

上記の_overload_t_に追加できる改善点がいくつかあります。

  • 構築中およびヘルパー関数でのfsの完全な転送。

  • 線形ではなくバランスのとれたバイナリツリーの継承(いくつかのオーバーロードが行われることが重要です)。これは、特に多数の関数の場合、ランタイムとコンパイル時のパフォーマンスに影響を与える可能性があります。

  • 着信Fsをイントロスペクトします;それらが_overload_t_の場合、結合されたツリーのバランスを取ります。

  • C++ 17では、関数ポインターを受け取り、それをステートレスに呼び出す関数オブジェクトを返す_func<auto>_テンプレート。コンパイラーは、関数ポインターを除外するのが比較的得意ですが、それらを変更する可能性のある実行時の状態がない場合は、より優れています。

  • _overload_t<>_の処理方法の決定。現在、コンパイルに失敗しています。たぶん、それは空の_struct {}_、あるいは呼び出し不可能なoperator()を持つ構造体であるべきです。

  • _boost::hana::overload_などの既存のライブラリを調べて、どのような違いがあるかを確認します。

  • 呼び出されるオーバーロードを抽出する機能を、おそらくstatic tag_t<F> which_overload_helper( Args... ) constメソッドとtemplate<class...Args> using which_overload = typename decltype( which_overload_helper( std::declval<Args>()... ) )::type;を介して公開します。

  • 着信Fssの一部にconstoperator()がない場合に、オーバーロードを適切に選択します。関数ポインターはconstvolatileoperator()の両方を持っている必要がありますか? 4つ全部? _&&_と_&_のオーバーロードはどうですか?

対象となる関数が多かったり、オーバーロードだけが解決しようとしていた問題ではない場合は、テンプレートを使用してこれを行う価値があるかもしれません。

ただし、この場合は、単にオーバーロードを記述します。

void foo(const char* x) { fooA(x); }
void foo(const wchar_t* x) { fooW(x); }
4
IanM_Matrix1

いくつかのオプションがあります。

  1. 明示的に特化したヘルパーを使用するstruct

    template <typename>
    struct helper;
    
    template<>
    struct helper<char>
    {
        void operator()(const char* x){ FooA(x); }
    };
    
    template<>
    struct helper<wchar_t>
    {
        void operator()(const wchar_t* x){ FooW(x); }
    };
    
    template <typename _TChar>
    void foo(const _TChar* str)
    {
        helper<_TChar>{}(str);
    }
    
  2. 「静的if」実装を使用します(例 boost :: hana :: eval_if または my own ):

    template <typename _TChar>
    void foo(const _TChar* str)
    {
        vrm::core::static_if(std::is_same<_TChar, char>{})
            .then([](const auto* x_str){ FooA(x_str); })
            .else_([](const auto* x_str){ FooW(x_str); })(str);
    }
    
  3. ヘルパーオーバーロード関数を使用します。

    void helper(const char* x) { FooA(x); }
    void helper(const wchar_t* x) { FooW(x); }
    
    template <typename _TChar>
    void foo(const _TChar* str)
    {
        helper(str);
    }
    
3
Vittorio Romeo