最終更新日時:
が更新

履歴 編集

範囲 for ループの制限緩和(C++17)

概要

範囲 for 文は C++11 で導入された が、 beginend の型が同じでなければならなかった。 C++17 でこの制限が緩和された。

仕様

for ( for-range-declaration : for-range-initializer ) statement

は以下のように展開される:

{
  auto && __range = for-range-initializer;
  auto __begin = begin-expr;
  auto __end = end-expr;     // __begin と __end は異なる型でもよい
  for ( ; __begin != __end; ++__begin ) {
    for-range-declaration = *__begin;
    statement
  }
}

参考

C++11 では以下のように展開されていた:

{
  auto && __range = for-range-initializer;
  for ( auto __begin = begin-expr, __end = end-expr; // __begin と __end は同じ型でなければならない
        __begin != __end;
        ++__begin ) {
    for-range-declaration = *__begin;
    statement
  }
}

#include <iostream>
#include <string>

// delimiter や終端に到達したかどうか判定する述語
template<char delimiter>
struct EndOfDelimitedString
{
  bool operator()(std::string::iterator it)
  {
    return *it != delimiter && *it != '\0';
  }
};

template<char delimiter>
struct DelimitedString
{
  std::string str;

  // DelimitedString::begin と DelimitedString::end の型は異なる
  std::string::iterator begin() { return str.begin(); }
  EndOfDelimitedString<delimiter> end() const { return EndOfDelimitedString<delimiter>(); }
};

template<char delimiter>
bool operator!=(std::string::iterator it, EndOfDelimitedString<delimiter> e)
{
  return e(it);
}

int main()
{
  std::string str{"ABCDE, abcde|12345"};

  for (auto c : str)
    std::cout << c;
  std::cout << '\n';

  for (auto c : DelimitedString<','>{str})
    std::cout << c;
  std::cout << '\n';

  for (auto c : DelimitedString<'|'>{str})
    std::cout << c;
  std::cout << '\n';
}

出力

ABCDE, abcde|12345
ABCDE
ABCDE, abcde

備考

GCC 4.9.3 でコンパイルすると beginend の型が異なるため不適格となる:

for.cpp: In function ‘int main()’:
for.cpp:38:41: error: inconsistent begin/end types in range-based ‘for’ statement: ‘std::basic_string<char>::iterator {aka __gnu_cxx::__normal_iterator<char*, std::basic_string<char> >}’ and ‘EndOfDelimitedString<','>’
   for (auto c : DelimitedString<','>{str})
                                         ^
for.cpp:38:41: error: conversion from ‘EndOfDelimitedString<','>’ to non-scalar type ‘std::basic_string<char>::iterator {aka __gnu_cxx::__normal_iterator<char*, std::basic_string<char> >}’ requested
for.cpp:42:41: error: inconsistent begin/end types in range-based ‘for’ statement: ‘std::basic_string<char>::iterator {aka __gnu_cxx::__normal_iterator<char*, std::basic_string<char> >}’ and ‘EndOfDelimitedString<'|'>’
   for (auto c : DelimitedString<'|'>{str})
                                         ^
for.cpp:42:41: error: conversion from ‘EndOfDelimitedString<'|'>’ to non-scalar type ‘std::basic_string<char>::iterator {aka __gnu_cxx::__normal_iterator<char*, std::basic_string<char> >}’ requested

この機能が必要になった背景・経緯

Range TS (technical specification) では Sentinel (番兵) 等の範囲の終端を表すコンセプトが提案されているが、 型が begin イテレータと異なるため範囲 for 文で使用できなかった (N4128R1 §3.3.5)。

end イテレータはインクリメント、デクリメント、間接参照されることがなく、この制限には実用的な意味がないため、緩和された。

検討されたほかの選択肢

Boost.Foreach のようなマクロは避けるべきだとされた。

関連項目

参照