2014年7月24日木曜日

【読書ノート】Effective Modern C++ - Item 3 [2 / 2]

Item 3の続きです。
以下のC++14の関数にまだ改善の余地があるというお話から。

universal referenceでrvalueをサポートする

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i);

この関数にはrvalueは渡せない。(Container&がconst参照でないため)
rvalueを渡すのはマイナーなケースだが、意味のある例は存在する

std::deque<std::string> makeStringDeque();
// dequeの5番目の要素のコピーを得る文
auto s = authAndAccess(makeStringDeque(), 5);

これをサポートするにはuniversal reference(詳細はItem 26)を使用すれば良い。

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i);

なお、Indexは標準ライブラリの例(std::string, std::vector, std::dequeなど)が合理的だと思えるため値渡しにしている。
加えてItem27の勧告に従ってuniversal referenceにstd::forwardを適用する。

template<typename Container, typename Index> // C++14の最終版
decltype(auto)
authAndAccess(Container& c, Index i)
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

template<typename Container, typename Index> // C++11の最終版
auto
authAndAccess(Container& c, Index i)
-> decltype(std::forward<Container>(c)[i])
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

decltypeの僅かな特殊なケース

decltypeは、変数名に関してはその変数の型を返すが、変数名以上に複雑なlvalueは全てlvalue referenceとして返す。
つまり、変数名以上に複雑な式のT型はT&型と型推定する。
しかしそのようなケースは少ない。(たとえば関数が返すlvalueはlvalue referenceである。さもなくばrvalue)
変数名以上に複雑なlvalueの例は、(x)だ。

int x = 0;
// decltype(x)はint
// decltype((x))はint&

decltype(auto) f1()
{
    int x = 0;
    ...
    return x; // f1の戻り値はint
}

decltype(auto) f2()
{
    int x = 0;
    ...
    return (x); // f2の戻り値はint&
}

f2の戻り値はローカル変数の参照なので、戻り値を受け取ったら未定義動作となってしまう!

注意点はあるが、通常の場合はdecltype(auto)は直感的な動作をする。
特に変数名に適用する場合は、decltypeという名前通り、変数の宣言された型(declared type)を返す。

Things to Remember

  • decltypeはほぼ常に変数や式の型を修正無しに生成する
  • 変数名より複雑なlvalue(たとえば"(x)")のTに対してdecltypeは常にT&を返す
  • C++14はdecltype(auto)をサポートし、decltypeのルールに従って型推定する

0 件のコメント:

コメントを投稿