2

2 つの異なる機能を持つテンプレート クラス (OutgoingPacket) があります。

void _prepare() {
    assert(false); // this should have been specialized and the native function never called.
}

template <typename Args, typename ... RemArgs>
void _prepare(Args&& args, RemArgs&& ... remArgs) {
    assert(false); // this should have been specialized and the native function never called.
}

次に、クラス定義の外で 2 つの特殊化を定義します。

// no args
template <> void OutgoingPacket<PacketServerAck> ::_prepare();
template <> void OutgoingPacket<PacketSup>       ::_prepare();
template <> void OutgoingPacket<PacketWelcome>   ::_prepare();
template <> void OutgoingPacket<PacketServerPing>::_prepare();

// with args
template <> template <> void OutgoingPacket<PacketGTFO>::_prepare<std::string>(std::string&& message);
template <> template <> void OutgoingPacket<PacketPlayer>::_prepare<std::shared_ptr<User>>(std::shared_ptr<User>&& user);

引数なしで準備するための関数呼び出しは期待どおりに機能しますが、引数付きのオーバーロードへの呼び出しは基本テンプレートを呼び出します。それらはアサートをトリガーします。

なぜこれが起こるのですか?


更新:特殊化の定義を変更して、同じ結果の参照を含めるようにしました:

template <> template <> void OutgoingPacket<PacketGTFO>::_prepare<std::string&&>(std::string&& message);
template <> template <> void OutgoingPacket<PacketPlayer>::_prepare<std::shared_ptr<User>&&>(std::shared_ptr<User>&& user);

余談ですが、私がこのようにしている理由は、基本クラスである OutgoingPacket に、これらのさまざまなバージョンの準備関数が散らばっているとは思わなかったからです。また、異なる OutgoingPackets 間の違いは非常に小さい (~4 行) ため、サブクラス化は適切ではないと感じました。

基本的に、OutgoingPacket オブジェクトは任意の引数を使用して作成され、準備関数に転送されます。

template<typename ... Args>
OutgoingPacket(Args&&... args) {
    _prepare(std::forward<Args>(args)...);
}

これが悪い習慣である場合、設計に関するアドバイスをいただけますか?

4

1 に答える 1

3

その理由は、右辺値ではない引数を使用して関数を呼び出している可能性が最も高いです。

主要な可変個引数テンプレートの関数パラメーターは右辺値と左辺値の両方にバインドされることに注意してください。つまり、それらはユニバーサル参照(Scott Meyers による非標準用語) であるのに対し、特殊化されたテンプレートの関数パラメーターは右辺値のみにバインドされます。

// The parameters are universal references
template <typename Args, typename ... RemArgs>
void _prepare(Args&& args, RemArgs&& ... remArgs) 

// The parameters are NOT universal references
template <> template <> 
void OutgoingPacket<PacketGTFO>::_prepare<std::string>(std::string&& message);

ユニバーサル リファレンスがどのように機能するかについては、この記事このプレゼンテーションを参照してください。トリッキーな部分は、&&接尾辞が常に右辺値参照を意味するとは限らないことです。たとえば、プライマリ テンプレートの場合はそうではありませんが、特殊化されたテンプレートの場合はそうです。

したがって、関数の入力引数が左辺値 (たとえば、関数呼び出しの戻り値ではなく名前を持つ変数) である場合、通常のオーバーロード解決規則により、特殊なテンプレートではなくプライマリ テンプレートにバインドされます。

于 2013-01-28T15:02:23.127 に答える