17

Herb Sutter の基本に戻る! CppCon での Essentials of Modern C++プレゼンテーションでは、パラメーターを渡すためのさまざまなオプションについて説明し、それらのパフォーマンスと記述/教育の容易さを比較しました。「高度な」オプション (テストされたすべてのケースで最高のパフォーマンスを提供しますが、ほとんどの開発者が書くには難しすぎる) は、次の例で完全な転送でした(PDF、28 ページ) :

class employee {
    std::string name_;

public:
    template <class String,
              class = std::enable_if_t<!std::is_same<std::decay_t<String>,
                                                     std::string>::value>>
    void set_name(String &&name) noexcept(
      std::is_nothrow_assignable<std::string &, String>::value) {
        name_ = std::forward<String>(name);
    }
};

この例では、テンプレート パラメーターStringenable_if. ただし、制約は正しくないようです。このメソッドは、String型が astd::stringでない場合にのみ使用できると言っているようですが、意味がありません。つまり、このstd::stringメンバーは値以外を使用して設定できますstd::string

using namespace std::string_literals;

employee e;
e.set_name("Bob"s); // error

私が考えた 1 つの説明は、単純なタイプミスがあり、制約が のstd::is_same<std::decay_t<String>, std::string>::value代わりになることを意図していたというものでし!std::is_same<std::decay_t<String>, std::string>::valueた。ただし、それはセッターが動作しないことを意味します。たとえば、const char *プレゼンテーションでテストされたケースの 1 つであることを考えると、明らかにこの型で動作することを意図していました。

正しい制約は次のように思われます。

template <class String,
          class = std::enable_if_t<std::is_assignable<decltype((name_)),
                                                      String>::value>>
void set_name(String &&name) noexcept(
  std::is_nothrow_assignable<decltype((name_)), String>::value) {
    name_ = std::forward<String>(name);
}

メンバーに割り当てることができるものはすべてセッターで使用できます。

適切な制約がありますか? 他に改善できる点はありますか?元の制約についての説明はありますか?おそらく文脈から外されたのでしょうか?


また、この宣言の複雑で「教えられない」部分が本当に有益なのだろうかと思います。オーバーロードを使用していないため、通常のテンプレートのインスタンス化に単純に依存できます。

template <class String>
void set_name(String &&name) noexcept(
  std::is_nothrow_assignable<decltype((name_)), String>::value) {
    name_ = std::forward<String>(name);
}

そしてもちろん、本当に重要かどうかについてはいくつかの議論がありnoexcept、move/swap プリミティブを除いて、あまり気にする必要はないと言う人もいます:

template <class String>
void set_name(String &&name) {
    name_ = std::forward<String>(name);
}

おそらく、概念があれば、単にエラー メッセージを改善するために、テンプレートを制約することは不当に難しくないでしょう。

template <class String>
  requires std::is_assignable<decltype((name_)), String>::value
void set_name(String &&name) {
    name_ = std::forward<String>(name);
}

これにはまだ仮想化できないという欠点と、ヘッダーになければならないという欠点がありますが (モジュールが最終的にその問題を解決することを願っています)、これはかなり教えられるようです。

4

3 に答える 3

4

あなたが持っているものはおそらく正しいと思いますが、単に「同意する」という「答え」を書かないために、代わりに正しいタイプに基づいて割り当てをチェックするこれを提案します-それがlval、rval、 const、何でも:

template <class String>
auto set_name(String&& name) 
-> decltype(name_ = std::forward<String>(name), void()) {
    name_ = std::forward<String>(name);
}
于 2014-10-01T18:33:53.707 に答える
4

私が考えた 1 つの説明は、単純なタイプミスがあり、制約が のstd::is_same<std::decay_t<String>, std::string>::value代わりになることを意図していたというものでし!std::is_same<std::decay_t<String>, std::string>::valueた。

はい、画面に表示された正しい制約は でありis_same、 ではありませんでし!is_sameた。スライドにタイプミスがあるようです。


ただし、それはセッターが機能しないことを意味します。const char *

はい、これは意図的に行われたと思います。のような文字列リテラルがユニバーサル参照"foo"を受け入れる関数に渡された場合、推定される型はポインターではなく (配列は値によってテンプレート パラメーターでキャッチされた場合にのみポインターに減衰するため)、むしろ. つまり、異なる長さの文字列リテラルを呼び出すたびに、次のような新しい特殊化がインスタンス化されます。const char(&)[N]set_nameset_name

void set_name(const char (&name)[4]); // set_name("foo");
void set_name(const char (&name)[5]); // set_name("foof");
void set_name(const char (&name)[7]); // set_name("foofoo");

制約は、右辺値引数の型、または左辺値引数のcv-のいずれかの型のみを受け入れて推定するように、ユニバーサル参照を定義することになっています (そのため、その条件で比較する前に ed になります)。std::stringstd::string&std::decaystd::stringstd::is_same


プレゼンテーションでテストされたケースの 1 つであることを考えると、明らかにこのタイプで動作することを意図していました。

テストされたバージョン (4) は制約されていなかったと思います ( String&&+perfect forwarding という名前だったことに注意してください)。

template <typename String>
void set_name(String&& name)
{
    _name = std::forward<String>(name);
}

そのため、文字列リテラルが渡されると、std::stringテンプレート化されていないバージョンと同じように、関数呼び出しの前にインスタンスを構築しません (呼び出し先のコードで不必要にメモリを割り当てて、std::string最終的には事前に割り当てられた宛先に移動される一時的なものを構築します) _name):

void set_name(const std::string& name);
void set_name(std::string&& name);

正しい制約は次のように思われます。 std::enable_if_t<std::is_assignable<decltype((name_)), String>::value>>

いいえ、私が書いたように、その意図は に代入可能な型を受け入れるように を制限することではなかった思いset_nameます。繰り返しますが、そのオリジナルは、 の右辺値と左辺値のみを受け入れるユニバーサル参照を取る関数の単一の実装を持つためにあります (これはテンプレートですが、それ以上のものはありません)。あなたのバージョンでは、代入できないものを渡すと、制約がそうであるかどうかに関係なく、エラーが発生します。非 const 右辺値参照の場合、最終的にはその引数を移動するだけになる可能性があることに注意してください。std::stringstd::enable_ifset_namestd::stringstd::enable_ifstd::stringname_name、そのため、SFINAE を使用してその関数をオーバーロード解決から除外し、他のオーバーロードを優先する場合を除いて、代入可能性のチェックは無意味です。


enable_if完全転送セッターの正しい制約は何ですか?

template <class String,
          class = std::enable_if_t<std::is_same<std::decay_t<String>,
                                                std::string>::value>>

または、パフォーマンスに影響を与えない場合は、制約はまったくありません (文字列リテラルを渡すのと同じように)。

于 2014-10-01T19:30:52.367 に答える