インデックス付きrange-based forに必要なのはネストされた構造化束縛ではなくてif constexprだった
親愛なるC++読者諸君、私がC++17を流暢に使いこなす江添亮である。前回、ネストされた構造化束縛がほしいと書いた。
本の虫: インデックス付きRange-based for文を実装したらネストされた構造化束縛が欲しくなった
このブログを公開してすぐ、同僚からwith_index側でpairを開けばいいのではないかと言われた。たしかにそのとおりだ。早速実装した。その結果、以下のコードが通るようになった。
int main()
{
std::map<int,int> m = { {1,1}, {2,2},{3,3} } ;
for ( auto[ i, key, mapped ] : with_index(m) )
{
std::cout << i << key << mapped ;
}
}
その実装の骨子は以下の通り。
template < typename T >
struct is_pair
: std::false_type { } ;
template < typename T1, typename T2 >
struct is_pair< std::pair<T1, T2> >
: std::true_type { } ;
template < typename T >
constexpr bool is_pair_v = is_pair<T>::value ;
template < typename Iterator >
class with_index_iterator
: public Iterator
{
public :
auto operator *() const noexcept
{ // ここが重要
if constexpr ( is_pair_v< typename std::iterator_traits<Iterator>::value_type > )
{
auto & pair = *static_cast<Iterator const &>(*this) ;
return std::make_tuple( i, pair.first, pair.second ) ;
}
else {
return std::make_pair( i, *static_cast<Iterator const &>(*this) ) ;
}
}
} ;
重要なのはif constexprだ。constexpr ifによって、わざわざクラステンプレートを書かずともコンパイル時条件分岐ができるようになった。これでメタプログラミングがとても書きやすくなった。
なお、この実装では以下のようなコードは通らない。
int main()
{
std::map<int, std::pair<int,int> > m = { {1,{1,1}}, {2,{2,2}},{3,{3,} } ;
for ( auto[ i, key, m1, m2 ] : with_index(m) )
{ }
}
再帰的なテンプレートメタプログラミングをすることにより、何段階にネストされようとも開くことができるwith_indexは実装可能だ。その実装は読者への課題とする。
また、構造化束縛がpairの他にも対応しているtupleやtuple_sizeとtuple_elementとgetに対応した型のネストへの対応も、読者への課題とする。
int main()
{
std::map<int, std::tuple<int,int> > m = { {1,{1,1}}, {2,{2,2}},{3,{3,} } ;
for ( auto[ i, key, m1, m2 ] : with_index(m) )
{ }
}
問題は、構造化束縛が対応しているクラスを開くことができない。
struct user_defined_pair
{
int x ;
int y ;
} ;
std::map< int, user_defined_pair > m = { {1,{1,1}}, {2,{2,2}},{3,{3,} } ;
これはどうしようもない。とはいえ、実用上はこれでいいのではないか。
with_indexの完全な実装は以下の通り。
template < typename T >
struct is_pair
: std::false_type { } ;
template < typename T1, typename T2 >
struct is_pair< std::pair<T1, T2> >
: std::true_type { } ;
template < typename T >
constexpr bool is_pair_v = is_pair<T>::value ;
template < typename Iterator >
class with_index_iterator
: public Iterator
{
std::size_t i = 0 ;
public :
with_index_iterator( Iterator iter )
: Iterator( iter )
{ }
auto & operator ++()
{
++i ;
this->Iterator::operator ++() ;
return *this ;
}
auto operator *() const noexcept
{
if constexpr ( is_pair_v< typename std::iterator_traits<Iterator>::value_type > )
{
auto & pair = *static_cast<Iterator const &>(*this) ;
return std::make_tuple( i, pair.first, pair.second ) ;
}
else {
return std::make_pair( i, *static_cast<Iterator const &>(*this) ) ;
}
}
} ;
template < typename Range >
class with_index
{
Range & range ;
public :
with_index( Range & range )
: range(range)
{ }
auto begin() const
{
return with_index_iterator{ std::begin(range) } ;
}
auto end() const
{
return with_index_iterator{ std::end(range) } ;
}
} ;
ドワンゴ広告
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0