Search code examples
c++c++11boostclang

Why does Clang not like boost::transform_iterator?


With Clang 8.0.1 and Boost 1.70, the following program

// transform.cpp
#include <vector>
#include <algorithm>
#include <iostream>

#include <boost/iterator/transform_iterator.hpp>

struct Foo
{
    int x;
};

struct XGetter
{
    auto operator()(const Foo& foo) const noexcept { return foo.x; }
};

int main()
{
    const std::vector<Foo> foos {{1}, {2}, {3}};
    using boost::make_transform_iterator;
    const auto first = make_transform_iterator(foos.cbegin(), XGetter {});
    const auto last = make_transform_iterator(foos.cend(), XGetter {});
    std::cout << *std::max_element(first, last) << std::endl;
}

fails to compile

$ clang++ -std=c++14 -o transform transform.cpp

/usr/local/Cellar/llvm/8.0.1/bin/../include/c++/v1/algorithm:2494:5: error: static_assert failed due to requirement
      '__is_forward_iterator<boost::iterators::transform_iterator<XGetter, std::__1::__wrap_iter<const Foo *>,
      boost::use_default, boost::use_default> >::value' "std::max_element requires a ForwardIterator"
    static_assert(__is_forward_iterator<_ForwardIterator>::value,
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/Cellar/llvm/8.0.1/bin/../include/c++/v1/algorithm:2512:19: note: in instantiation of function template
      specialization 'std::__1::max_element<boost::iterators::transform_iterator<XGetter, std::__1::__wrap_iter<const
      Foo *>, boost::use_default, boost::use_default>, std::__1::__less<int, int> >' requested here
    return _VSTD::max_element(__first, __last,
                  ^
transform.cpp:24:24: note: in instantiation of function template specialization
      'std::__1::max_element<boost::iterators::transform_iterator<XGetter, std::__1::__wrap_iter<const Foo *>,
      boost::use_default, boost::use_default> >' requested here
    std::cout << *std::max_element(first, last) << std::endl;
                       ^
1 error generated.

I was under the impression that boost::transform_iterator inherited the iterator category of iterator it models. What is going wrong?


Solution

  • The (pre-C++20) standard requires forward-or-stronger iterators:

    • to produce a real reference when dereferenced;
    • to produce references to the same object when two equal iterators are dereferenced (i.e., no stashing)

    Since your transformation returns by value, there's no way for transform_iterator to meet both requirements. Thus, it can only advertise itself as an input iterator.

    The fix is to change XGetter to return by reference, or to use std::mem_fn(&Foo::x) which does that for you.