2015-01-09 10 views
5

タスクは、すべての型を1つ(Foo)から離れて転送する単一引数の関数を作成し、それを(Barに)変換することです。選択的転送機能

(FooからBarへの変換があるとします)ここで

は、利用シナリオです:

template<typename Args...> 
void f(Args... args) 
{ 
    g(process<Args>(args)...); 
} 

(私は元のコンテキストhereからそれを簡素化/抽出するために試してみた - 私はミスを犯してしまった場合、誰かが私に教えてください。!)

template<typename T> 
T&& process(T&& t) { 
    return std::forward<T>(t); 
} 

Bar process(Foo x) { 
    return Bar{x}; 
} 

そして...

:ここ

は、2つの可能な実装があります

template <typename T, typename U> 
T&& process(U&& u) { 
    return std::forward<T>(std::forward<U>(u)); 
} 

template <typename T> 
Bar process(Foo x) { 
    return Bar{x}; 
} 

私は良い権限(here)で2番目の方が望ましいとしています。

しかし、私は説明を理解できません。私はこれがC++の最も暗いコーナーのいくつかを掘り下げていると思います。

私は、何が起こっているのかを理解するために必要な機械が不足していると思います。誰かが詳細に説明できますか?それがあまりにも掘り出している場合、誰もが必要な前提条件の概念を学ぶためのリソースをお勧めできますか?

編集:私の場合、関数シグネチャはtypedef -sのいずれかと一致するように、this pageに追加したいと思います。つまり、すべての議論は、PyObject*PyObjectは通常のC構造体)またはconst char*int,floatのような基本C型のいずれかになります。だから私の推測では、軽量実装が最も適切かもしれないということです(私は過大化のファンではありません)。しかし、私は本当にこれらの問題を解決するための正しい考え方を得ることに興味があります。

+2

これらの例では、プロセス関数が呼び出されるコンテキストがありません - 常に明示的な型テンプレート引数を使用します(少なくとも非左辺参照をxvaluesに変換するという考え方でした)。あなたはあなたのバージョンを手放すことができますが、ターゲット関数のパラメータを初期化している間は常にcopy ctorを呼び出すでしょう –

+0

バージョン1は何らかの形で「汚い」ということに同意します。なぜなら、テンプレート化されていない関数のオーバーロード戻り値の型。しかし、バージョン2では何の意味も感じられません。コンパイラは、使用状況でそれを推論することができないため、常に最初のテンプレートパラメータを指定する必要があります。 –

+0

@PiotrS。、私は文脈を提供するために編集しました(私の実際のユースケースにマッチすると思います)。これらの最後のカップルのあなたの貴重な助けのおかげで、私は現在実用的なソリューションを持っています([こちら](http://stackoverflow.com/q/27866483/435129))。これは私が理解していないコードを組み込むのが好きではないので、私が噛んでいる最後のコンポーネントです。 –

答えて

2

私は、あなたが直面しているユースケースを理解していないことを少し誤解しています。

まず第一に、これは関数テンプレートです:

struct A 
{ 
    template <typename... Args> 
    void f(Args... args) 
    { 
    } 
}; 

そして、これは関数テンプレートではありません:(関数テンプレート付き)旧定義では

template <typename... Args> 
struct A 
{ 
    void f(Args... args) 
    { 
    } 
}; 

引数の型推論起こる。後者の場合、タイプ控除はありません。

機能テンプレートは使用していません。クラステンプレートから非テンプレートメンバー関数を使用しています。この特定のメンバー関数については、その署名は修正されています。

template <typename T, T t> 
struct trap; 

template <typename R, typename... Args, R(Base::*t)(Args...)> 
struct trap<R(Base::*)(Args...), t> 
{  
    static R call(Args... args); 
}; 

とそのメンバ関数を参照する以下のような:

&trap<decltype(&Base::target), &Base::target>::call; 

あなたは、静的な非テンプレートcall関数へのポインタ付きで終わる以下のようなあなたのtrapクラスを定義することで

固定の署名で、target関数の署名と同じです。

今、そのcall関数は、中間呼び出し側として機能します。あなたはcall関数を呼び出すされ、その機能はtargetのパラメータを初期化するために、独自の引数を渡す、targetメンバ関数を呼び出します、と言う:

template <typename R, typename... Args, R(Base::*t)(Args...)> 
struct trap<R(Base::*)(Args...), t> 
{  
    static R call(Args... args) 
    { 
     return (get_base()->*t)(args...); 
    } 
}; 

trapクラステンプレートをインスタンス化するために使用target関数であると仮定します定義された次のように

struct Base 
{ 
    int target(Noisy& a, Noisy b); 
}; 

を使用すると、以下のcall機能で終わるtrapクラスをインスタンス化することによって:

幸いにも10
// what the compiler *sees* 
static int call(Noisy& a, Noisy b) 
{ 
    return get_base()->target(a, b); 
} 

aを参照でを渡され、それは単にtargetのパラメータで参照の同じ種類によって転送され、結合されます。残念ながら、これはbオブジェクトのために保持していない - に関係なくNoisyクラスはそのいずれかが値を渡されるので、あなたは、bインスタンスの複数のコピーを作っている、移動可能であるかどうか:

  • 最初のもの:call関数が外部コンテキストから呼び出されたとき。

  • callの本体からtarget関数を呼び出すときにbインスタンスをコピーする場合。

DEMO 1

これはやや非効率的である:あなたがにはxValueをbインスタンスを回すことができる場合にのみ、移動、コンストラクタ呼び出しにそれを回す、少なくとも1つのコピー・コンストラクタ呼び出しを保存している可能性が

これで、2番目のパラメータの代わりに移動コンストラクタが呼び出されます。

これまでのところよくできていましたが、手動で行われました(std::moveは、移動セマンティクスを適用するのが安全だと知りました)。さて、質問はパラメータパック上で動作しているときと同じ機能が適用され得るか、次のとおりです。?

return get_base()->target(std::move(args)...); // WRONG! 

あなたはstd::moveそれぞれの呼び出しやパラメータパック内のすべての引数を適用することはできません。これはおそらくすべての引数に等しく適用されるとコンパイラエラーを引き起こすでしょう。幸い

DEMO 2

Args...転送参照ないにもかかわらず、std::forwardヘルパー関数が代わりに使用することができます。左辺値参照の

  • を(例えばTNoisy&の場合)::それはstd::forwardは異なる動作をします(左辺値参照または非左辺値参照)<T>タイプがstd::forward<T>であるものに応じて、ある値式のカテゴリは左辺値のままです(つまり、Noisy&)。非左辺値、参照用

  • TNoisy&&またはプレーンNoisyであれば例えば)式の値カテゴリはxValue(すなわち、Noisy&&)となります。

    static R call(Args... args) 
    { 
        return (get_base()->*t)(std::forward<Args>(args)...); 
    } 
    

    あなたがで終わる:はxValueにbを含む式の値カテゴリを回す

    static int call(Noisy& a, Noisy b) 
    { 
        // what the compiler *sees* 
        return get_base()->target(std::forward<Noisy&>(a), std::forward<Noisy>(b)); 
    } 
    

    以下のようなtarget機能を定義することによって、それは言って

b、これはNoisy&&です。これにより、コンパイラはaのままにして、target関数の2番目のパラメータを初期化するために移動コンストラクタを選択できます。

DEMO 3(DEMO 1と出力を比較)

基本的に、これはstd::forwardのためにあるものです。通常、std::forward転送参照と使用され、Tは転送参照のタイプ控除の規則に従って推測されるタイプを保持します。その型に応じて(引数の値のカテゴリに依存しないで)異なる動作を適用するので、常に<T>部分を明示的に渡す必要があることに注意してください。明示的な型テンプレートの引数<T>がなければ、std::forwardは、(パラメータパックを展開するときのように)名前によって参照される引数の左辺参照を常に推測します。

さらに、は、あるタイプから別のタイプへの引数の一部を変換し、他のタイプはすべて転送します。あなたは、パラメータパックからstd::forward ING引数を持つトリックを気にしない、それは常にコピーコンストラクタを呼び出すために罰金だ場合は、お使いのバージョンはOKです:しかし

template <typename T>   // transparent function 
T&& process(T&& t) { 
    return std::forward<T>(t); 
} 

Bar process(Foo x) {   // overload for specific type of arguments 
    return Bar{x}; 
} 

//... 
get_base()->target(process(args)...); 

DEMO 4

デモでそのNoisy引数のコピーを避けたい場合、あなたはstd::forwardが適切な行動(トンを適用することができるように何とか、Args種類以上processコール、パスでstd::forwardコールを結合する必要がありますX値に変換するか何もしない)。これを実装する方法の簡単な例を説明しました。

template <typename T, typename U> 
T&& process(U&& u) { 
    return std::forward<T>(std::forward<U>(u)); 
} 

template <typename T> 
Bar process(Foo x) { 
    return Bar{x}; 
} 

//... 
get_base()->target(process<Args>(args)...); 

これは単なる1つのオプションです。それは、単純化し、書き換え、または並べ替え、あなたがprocess機能(バージョン)を呼び出す前に、std::forwardが呼び出されるようにすることができます。

get_base()->target(process(std::forward<Args>(args))...); 

DEMO 5(DEMO 4との出力を比較)

そして、それあなたのバージョンでもうまく動作します。つまり、追加のstd::forwardはコードを少し最適化するだけで、provided ideaはその機能の実装の1つに過ぎません(これは同じ効果をもたらします)。

0

バージョン2の最初の部分ではありませんか?のみ:あなたが最初のテンプレートパラメータを指定することを余儀なくされている

struct Foo { 
    int x; 
}; 
struct Bar { 
    int y; 
    Bar(Foo f) { 
     y = f.x; 
    } 
}; 
int main() { 

    auto b = process<Bar>(Foo()); // b will become a "Bar" 
    auto i = process<int>(1.5f); 
} 

(タイプ:

template <typename T, typename U> 
T&& process(U&& u) { 
    return std::forward<T>(std::forward<U>(u)); 
} 

のような(「フー」から「バー」のコンストラクタ)は、既存の変換での使用の場合に、与えられましたコンパイラはそれを推論することができないので、とにかく)に変換する。したがって、あなたはどのタイプが期待されているかを知っていて、コンストラクタがあるので、タイプ "Bar"の一時オブジェクトを構築します。

+0

によって呼び出されることはありません。質問が明確になり、あなたの答えは(残念ながら)間違っています。 –