Re: want to pass vector<foo*> to fn expecting vector<const foo*>
{ Please limit your quoting to the minimum needed to establish context
-mod }
On 4/18/2013 11:45 PM, Jonathan Thornburg wrote:
Consider the following toy program:
[Disclaimer: This is an abstraction of a design problem I currently face
in "real" code. This is not a homework assignment.]
--- begin sample code ---
// investigate passing vector<foo*> to a function expecting
vector<const foo*>
#include <cassert>
#include <vector>
#include <iostream>
using std::cout;
class interval
{
public:
int min() const { return min_; }
int max() const { return max_; }
void make_empty() { min_ = 0; max_ = -1; }
interval(int min_in, int max_in)
: min_(min_in), max_(max_in)
{ /* empty constructor body */ }
// default compiler-generated destructor is ok
// default compiler-generated copy ctor & assignment op are ok
private:
int min_, max_;
};
// prototype
void print_vector_of_intervals(const std::vector</* const */
interval*>& vci);
int main()
{
std::vector<interval*> vi;
vi.push_back(new interval(2,3));
vi.push_back(new interval(5,7));
vi.push_back(new interval(11,13));
print_vector_of_intervals(vi);
}
// print each interval pointed-to by a member of vci
// n.b. this function promises not to change the vector-of-pointers
// AND not to change the pointed-to intervals
void print_vector_of_intervals(const std::vector</* const */
interval*>& vci)
{
for (int i = 0 ; i < static_cast<int>(vci.size()) ; ++i)
{
const interval* pI = vci.at(i);
assert(pI != NULL);
const interval& I = *pI;
cout << "interval " << i << " = "
<< "[" << I.min() << ", " << I.max() << "]" << "\n";
}
}
--- end sample code ---
As written, the program is accepted without complaint by g++ 4.6.2 and
clang++ 3.0 (both with -W -Wall), and produces the expected output when
run.
However, as written there's a design weakness in the program:
print_vector_of_intervals() promises in its header comment not to change
the pointed-to intervals, but its prototype doesn't reflect that promise.
So, the "obvious" solution is to uncomment the commented-out "const" in
print_vector_of_intervals()'s prototype and its declaration, so that the
prototype looks like this:
void print_vector_of_intervals(const std::vector<const interval*>& vci);
Now the prototype makes explicit the semantics which were previous only
described in the comments, namely that print_vector_of_intervals() won't
modify the pointed-to intervals. (More precisely, that it won't call any
non-const interval:: member functions (such as interval::make_empty())
on the pointed-to intervals.)
Alas, now main isn't allowed to pass a std::vector<interval*> to
print_vector_of_intervals(): Both g++ and clang++ agree that the
modified
code is invalid, because there's no known conversion from
std::vector<interval*>
to
std::vector<const interval*>
My basic question is, what to do about this? That is, what design(s)
can/should be used (in any/all of C++98, 03, or 11) so that client code
which has a std::vector<interval*> can pass (a reference to) that
vector
to a function which promises not to change either the vector-of-pointers
or the pointed-to objects?
I can think of two obvious solutions... each with fairly obvious
drawbacks:
(a) omit the "const" in the prototype & declaration of
print_vector_of_intervals()
(b) have client code copy the pointers to a temporary
vector-of-pointers-to-const-intervals, and then call
print_vector_of_intervals() on that temporary
Is there an elegant solution that I've overlooked?
[Of course for this toy program, it's easy to just have a vector of
intervals rather than a vector of pointers-to-intervals, but in my
"real" code the objects in question are large and noncopyable, so
vector-of-pointers is the appropriate data structure.]
Along the lines of Chris' posting, making Interval output streamable you
can use boost range as below:
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/indirected.hpp>
#include <boost/range/algorithm/copy.hpp>
std::ostream& operator<<(std::ostream& os, Interval const& i)
{
os << "interval " << " = [" << i.min() << ", " << i.max() << "]";
}
struct is_not_null_ptr
{
template<T> bool operator()( T t ) const { return !!t; }
};
using namespace boost::adaptors;
using namespace boost;
copy( v | filtered(is_not_null()) | indirected
, std::ostream_iterator<Interval>(std::cout, "\n"));
This separates out streaming from the filtering and deref'ing. The
streaming operator promises not to modify the interval. std::copy, which
boost::range::copy emulates is a non-modifying algorithm.
IMO a better approach is encapsulate the std::vector<interval*> rather
than trying to convert to std::vector<const interval*>, thus ensuring
valid invariants. For example the filtered(is_not_null()) would not be
needed if your class invariant was for all i in v<interval*> i != 0.
Jeff
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]