0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Effective Modern C++ のサンプルコードの誤り?

Last updated at Posted at 2021-09-06

「Effective Modern C++」 は、名著 「Effective C++」 のC++11/C++14対応版で、こちらもためになる内容が多く何度も読み返しています。
その中で一点、掲載されているサンプルコード(というか解説?)に誤りがあるのではないかという点があります。

「項目3: decltypeを理解する」で decltype(auto) の解説で以下のようなコードが示されます。

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
  authenticateUser();
  return std::forward<Container>(c)[i];
}

このコードは、もともと引数cの型がContainer&で左辺値参照であったコードからContainer&&のユニヴァーサル参照に変更したもので、戻り値もc[i]だったものをstd::forward<Container>(c)[i]に変更しており、「項目25の忠告にもあるように、テンプレートの実装にはユニヴァーサル参照をstd::forwardするように変更する必要があります。」と高らかに宣言しています。
ですが、よく見るとstd::forwardしているのはコンテナのcであり、return しているのはc[i]です。いくらcを完全転送たところで、返しているのはそのcの配列に含まれる要素の一つなので、これではstd::forwardしていないのと全く同じ事だと思えます。

実際、c自体は右辺値として転送されずこの関数終了時に削除されてしまうので、

auto&& hoge = authAndAccess(std::vector<int>{1, 2, 3}, 0);
std::cout << hoge << std::endl;

のようなコードはhogeが破棄された領域を参照しており、未定義動作になります。
もちろん、

auto hoge = authAndAccess(std::vector<int>{1, 2, 3}, 0);

のように書けば値がコピーされるので大丈夫ですが、それだとそもそも std::forward する必要が無いですよね。

C++の超有名な本を書く人でもすみずみまでは理解できないほど、C++は難しいという事の証左でしょうか。

じゃあどう書けばいいのか

結局本来やりたかった事は、cに左辺値が渡された場合はコンテナの要素の参照を返し、右辺値が渡された場合はコンテナの要素をムーブして返したい、という事なのだと思います。上述した例は整数の配列なのでコピーでも問題ありませんが、巨大なオブジェクトだとコピーのオーバーヘッドを避けたいというのがあります。
じゃあこれを実現するためにはどう書けばいいのか、と頭をひねって絞り出したコードはこんな感じになりました。

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
  authenticateUser();
  if constexpr (std::is_lvalue_reference_v<Container>) {
    return c[i];
  } else {
    return typename Container::value_type(std::move(c[i]));
  }
}

std::is_lvalue_reference_vで左辺値が渡されたかどうかをチェックし、左辺値ならばコンテナ要素の参照を返し、右辺値なら新たにオブジェクトをムーブコンストラクタで生成して返す(RVOにより最適化されるので戻り値に直接ムーブコンストラクトされる)、というものです。(if constexpr を使うのでC++17が必要ですが…)

もっといい書き方がありそうです。

0
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?