What are the benefits of using boost::any_range
?
Here is an example:
typedef boost::any_range<
int
, boost::forward_traversal_tag
, int
, std::ptrdiff_t
> integer_range;
void display_integers(const integer_range& rng)
{
boost::copy(rng,
std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl;
}
int main(){
std::vector<int> input{ ... };
std::list<int> input2{ ... };
display_integers(input);
display_integers(input2);
}
But the same functionality with more efficiency can be achieved with a template parameter, which satisfies the ForwardRange concept:
template <class ForwardRange>
void display_integers(const ForwardRange& rng)
{
boost::copy(rng,
std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl;
}
So I am searching for scenarios when it is worth to use any_range. Maybe I am missing something.
This technique is called Type Erasure. There is a full article describing the pros and cons on the example of any_iterator
: On the Tension Between Object-Oriented and Generic Programming in C++.
It is possible to hide (in a separate file/library) the implementation/definition of
void display_integers(const integer_range& rng)
But in the case of
template <class ForwardRange>
void display_integers(const ForwardRange& rng)
you have to provide source code to users (or at least make explicit instantiations somewhere).
Moreover, in the first case, display_integers
will be compiled only once, but in the second it will be compiled for every type of the passed range.
Also, you may have somewhere
integer_range rng;
and during lifetime of rng
you may assign ranges of different types to it:
vector<int> v;
list<int> l;
integer_range rng;
rng = v;
rng = l;
The biggest disadvantage of type erasure is its runtime cost; all operations are virtual, and cannot be inlined (easily).
P.S. another famous example of type erasure is std::function
boost::any_range
can used for returning ranges from functions. Imagine the following example:
auto make_range(std::vector<int> v) -> decltype(???)
{
return v | filter([](int x){ return x % 2 == 0;})
| transform([](int x){ return x * 2;});
}
*: gcc does not compile the above without wrapping it in std::function
, hower clang 3.2 works by directly passing the lambda
It is very difficult to know what is being returned from this function. Also, lambda and decltype don't work together so we cannot deduce the type using decltype
when passing only a lambda. One solution is to use boost::any_range
like the one in your example (another workaround is to use std::function
as pointed out by Evgeny Panasyuk in the comments):
integer_range make_range(std::vector<int> v)
{
return v | filter([](int x){ return x % 2 == 0;})
| transform([](int x){ return x * 2;});
}
Working example with gcc using std::function
.
Working example with clang passing lambdas directly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With