Limitations / Flaws with transformed_range
I've already noted that the "transformed_range", the class behind boost::adaptors::transform, is not using result_of as documented but seems to be simply looking for result_type, period. I've found another issue, and I consider it a "flaw" because it is a Limitation due to oversight or an artificial restriction, and the deeper workings should handle these cases just fine. Let me explain in detail: The template< class F, class R > struct transformed_range : takes two arguments. The R argument is used for two purposes. It gives the iterator type that will be held, via range_iterator<R>::type. It also gives the exact type of the argument expected by the constructor. Now here is an example from my experiments / work-in-progress: template< class Range > inline transformed_range<ASCII_lower,const Range> operator|( const Range& r, const ASCII_lower_forwarder ) { return transformed_range<ASCII_lower,const Range>( ASCII_lower(), // The underlying transform iterators wants this internal::src_prep(typename std::tr1::is_pointer<Range>::type() ,r) // "source" style argument processing ); } The src_prep is similar to the supplied is_literal, and used for the same purpose. It will package a primitive array as a iterator_range, handing string literals and primitive array objects in the way I intend with respect to nul terminators. It differs from is_literal in several ways, but the idea is the same. Now I don't have to do anything to the Range parameter passed as the type argument to transformed_range, because even when I change the type of the massaged argument, it still has the same underlying iterator type. After all, it gets the iterators from the original range thing passed in. But, the massaged value of r is rejected by the constructor, because it has a different type. It doesn't need to be the same type! It has the same underlying iterator type and could be assigned to it, but the constructor is too strict. It would be a simple matter of passing another template argument to the constructor, as is commonly done with constructors or assignment, and take anything that "works" in that boost::begin and boost::end returns something that can be used to initialize the range_iterator. Copying the class to my own code and changing the constructor to: template <typename R2> transformed_range( F f, R2& r ) : base( boost::make_transform_iterator( boost::begin(r), f ), boost::make_transform_iterator( boost::end(r), f ) ) indeed makes it acceptable to the compiler. (At this point I also noticed that _some_ of the types within the class are qualified by boost::, but not all of them. I know that functions are qualified to prevent argument-dependant lookup, but it is odd that some of the types (or names used in other context) are qualified and sometimes they are not.)
Hi,
I've already noted that the "transformed_range", the class behind boost::adaptors::transform, is not using result_of as documented but seems to be simply looking for result_type, period.
boost::adaptors::transform using result_of now(Boost 1.47.0). see release note: http://www.boost.org/users/history/version_1_47_0.html
Iterator: Add function_input_iterator (#2893). Bug fixes: #1427, #1517, #3434.
and change: https://svn.boost.org/trac/boost/ticket/1427
======================== Akira Takahashi mailto:faithandbrave@gmail.com blog: http://d.hatena.ne.jp/faith_and_brave/
I've found another issue, and I consider it a "flaw" because it is a Limitation due to oversight or an artificial restriction, and the deeper workings should handle these cases just fine. Let me explain in detail:
The template< class F, class R > struct transformed_range :
takes two arguments. The R argument is used for two purposes. It gives the iterator type that will be held, via range_iterator<R>::type. It also gives the exact type of the argument expected by the constructor.
Now here is an example from my experiments / work-in-progress:
template< class Range > inline transformed_range<ASCII_lower,const Range> operator|( const Range& r, const ASCII_lower_forwarder ) { return transformed_range<ASCII_lower,const Range>( ASCII_lower(), // The underlying transform iterators wants this internal::src_prep(typename std::tr1::is_pointer<Range>::type() ,r) // "source" style argument processing ); }
The src_prep is similar to the supplied is_literal, and used for the same purpose. It will package a primitive array as a iterator_range, handing string literals and primitive array objects in the way I intend with respect to nul terminators. It differs from is_literal in several ways, but the idea is the same.
Now I don't have to do anything to the Range parameter passed as the type argument to transformed_range, because even when I change the type of the massaged argument, it still has the same underlying iterator type. After all, it gets the iterators from the original range thing passed in.
But, the massaged value of r is rejected by the constructor, because it has a different type. It doesn't need to be the same type! It has the same underlying iterator type and could be assigned to it, but the constructor is too strict.
I don't think this "operator|" - which lives in the boost::range_detail namespace (note the "detail") - is meant to be specialized by users. Why not accomplish this "massaging" through your own range adaptor instead? As in: my_range | my_massager | transformed(ASCII_lower()) If you use this a lot, you could write a range adaptor that combines the two: my_range | ascii_lowered Regards, Nate.
On 7/19/2011 3:41 AM, Nathan Ridge wrote:
I don't think this "operator|" - which lives in the boost::range_detail namespace (note the "detail") - is meant to be specialized by users.
On the contrary. It's not specialized, but overloaded, and the instructions say to do exactly that. In particular, <http://www.boost.org/doc/libs/1_46_1/libs/range/doc/html/range/reference/extending/method_3/method_3_1.html> "Implement a Rage Adaptor without arguments".
Why not accomplish this "massaging" through your own range adaptor instead?
The Range docs on is_literal explain that it's used inside an algorithm implementation, not explicitly by the caller. I'm using my "prep" functions based on the nature of the argument, treating them in different ways. The ability to deal with the argument in that way is a documented part of the algorithm. The call to it is rather baroque rather than wrapped in another function that dispatches based on the tagging, because one of the differences from as_literal is that it doesn't always return the same type. as_literal reforms whatever you gave it into a iterator_range. src_prep only packs up primitive arrays and leaves other things _unchanged_. The thought is that I can pass single-use ranges and not trigger them prematurely. The simple-to-call wrapper would need another metafunction to form the return type, so I didn't bother.
I don't think this "operator|" - which lives in the boost::range_detail namespace (note the "detail") - is meant to be specialized by users.
On the contrary. It's not specialized, but overloaded, and the instructions say to do exactly that. In particular, <http://www.boost.org/doc/libs/1_46_1/libs/range/doc/html/range/reference/extending/method_3/method_3_1.html> "Implement a Rage Adaptor without arguments".
I'm sorry, I misread your code. I saw the return type was transformed_range and so I thought you are somehow trying to specialize the "transformed" adaptor's operator| to behave differently when you do "my_range | transformed(ASCII_lower())". Looking at it again, it seems what you are really trying to do is write your own adaptor "ASCII_lowered" or something similar that would be used as "my_range | ASCII_lowered" (which is precisely what I was suggesting). Now, regarding your problem (which I now understand):
template< class Range > inline transformed_range<ASCII_lower,const Range> operator|( const Range& r, const ASCII_lower_forwarder ) { return transformed_range<ASCII_lower,const Range>( ASCII_lower(), // The underlying transform iterators wants this internal::src_prep(typename std::tr1::is_pointer<Range>::type() ,r) // "source" style argument processing ); }
As you discovered, returning transformed_range<ASCII_lower, const Range> won't work because src_prep() does not return "const Range", it returns something else. I see 3 solutions: 1) Write a metafunction "src_prep_result" that computes the return type of src_prep() on input Range, and the return: transformed_range<ASCII_lower, typename src_prep_result<Range>::type> 2) [C++0x only] Use decltype to figure out what src_prep returns, i.e. return: transformed_range<ASCII_lower, decltype(internal::src_prep(typename std::tr1::is_pointer<Range>::type(), r))> If you can't use C++0x, you may want to try the BOOST_TYPEOF() macro which emulates it. I'm not sure how well it works. 3) If, as you say, the iterator types of "Range" and the return type of src_prep() are the same, you can return an iterator_range, as follows (untested): template <typename Range> iterator_range<typename range_iterator<Range>::type> convert_to_iterator_range(const Range& r) { return iterator_range<typename range_iterator<Range>::type>(begin(r), end(r)); } template< class Range > inline transformed_range<ASCII_lower, const iterator_range<typename range_iterator<Range>::type> > operator|( const Range& r, const ASCII_lower_forwarder ) { return transformed_range<ASCII_lower, const iterator_range<typename range_iterator<Range>::type> >( ASCII_lower(), convert_to_iterator_range(internal::src_prep(typename std::tr1::is_pointer<Range>::type(), r)) ); } Regards, Nate.
On 7/19/2011 6:10 PM, Nathan Ridge wrote:
Looking at it again, it seems what you are really trying to do is write your own adaptor "ASCII_lowered" or something similar that would be used as "my_range | ASCII_lowered" (which is precisely what I was suggesting).
Right.
As you discovered, returning transformed_range<ASCII_lower, const Range> won't work because src_prep() does not return "const Range", it returns something else.
I see 3 solutions:
Updating the transformed_range constructor to take a template argument, as I showed, works perfectly, and is simpler than anything else. That's why I suggest that this be applied to the Boost code. Is there a way for a mere user to submit that formally?
On Wed, Jul 20, 2011 at 11:26, John M. Dlugosz <mpbecey7gu@snkmail.com> wrote:
Updating the transformed_range constructor to take a template argument, as I showed, works perfectly, and is simpler than anything else. That's why I suggest that this be applied to the Boost code. Is there a way for a mere user to submit that formally?
you can submit it to the trac tracker. Best, Dee
Updating the transformed_range constructor to take a template argument, as I showed, works perfectly, and is simpler than anything else.
This only works if the other range type happens to have the same iterator type. IMO this is a rare situation - most range types have their own iterator types.
That's why I suggest that this be applied to the Boost code. Is there a way for a mere user to submit that formally?
You can open a new ticket in the Boost Issue Tracker [1], and attach a patch implementing this. Regards, Nate. [1] https://svn.boost.org/trac/boost
participants (4)
-
Akira Takahashi
-
Diederick C. Niehorster
-
John M. Dlugosz
-
Nathan Ridge