Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

want to pass vector<foo*> to fn expecting vector<const foo*>

203 views
Skip to first unread message

Jonathan Thornburg

unread,
Apr 18, 2013, 11:45:17 PM4/18/13
to
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.]

thanks,

--
-- "Jonathan Thornburg [remove -animal to reply]" <jth...@astro.indiana-zebra.edu>
Dept of Astronomy & IUCSS, Indiana University, Bloomington, Indiana, USA
on sabbatical in Canada starting August 2012
"Washing one's hands of the conflict between the powerful and the
powerless means to side with the powerful, not to be neutral."
-- quote by Freire / poster by Oxfam


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Ulrich Eckhardt

unread,
Apr 19, 2013, 8:04:21 AM4/19/13
to

Am 19.04.2013 05:45, schrieb Jonathan Thornburg:
> void print_vector_of_intervals(const std::vector</* const */
interval*>& vci)
[...]
> 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*>

A vector<T const*> is not a baseclass or implicitly convertible from a
vector<T*>. The const of the passed vector is only applied to its
members, but not to the objects that these members point to. The
compilers are right, and this is a known issue.

> 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?

Well, the first solution is not to store unsafe raw pointers in the
first place. If you stored plain objects, the const would propagate
from the container to its elements.

If the types are polymorphic or you must avoid copying for some other
reason, this is not that easy though. In that case, you can use the
handle/body idiom: Instead of storing a raw pointer, you store a thin
wrapper that makes sure that you can only use const members of the
target object on a const wrapper:

struct interval_handle
{
explicit interval_handle(interval* i):
body(i)
{}
interval* operator->()
{
return body;
}
interval const* operator->() const
{
return body;
}
private:
interval* body;
};

Note that if you have a non-const reference, calling a function that
is overloaded by just the const-ness, the compiler will always select
the non-const one.

Further, using unsafe raw pointers is a bad idea obviously. For that
reason, I'd take the opportunity to use some kind of smart pointer,
too.

Greetings from Hamburg!

Uli


--

Martin Ba

unread,
Apr 19, 2013, 8:10:28 AM4/19/13
to

On 19.04.2013 05:45, Jonathan Thornburg wrote:
> Consider the following toy program:
>
> ...
>
> 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?
>

Maybe boost::ptr_vector or Boost Range can help?


--

Francis Glassborow

unread,
Apr 19, 2013, 11:47:42 AM4/19/13
to
There is a very real hidden cost in using indirection for placing objects in a container (i.e. using pointers of some form rather than the actual objects); the loss of locality. In a world of multi-level caches, cache thrashing can very seriously degrade performance. This can be so serious that copying becomes a viable alternative.

Of course, sometimes the programmer has no choice but designs that result in vectors of pointers (raw, dumb or smart) need to be viewed with suspicion if the application is in any way sensitive to performance.

Francis

MJanes

unread,
Apr 20, 2013, 1:46:21 PM4/20/13
to
On Friday, April 19, 2013 1:10:05 PM UTC+2, Ulrich Eckhardt wrote:
> In that case, you can use the
> handle/body idiom: Instead of storing a raw pointer, you store a thin
> wrapper that makes sure that you can only use const members of the
> target object on a const wrapper

alternatively, one could take the other way around and write a
container adaptor exposing a view of its dereferenced elements ( ala
boost::indirect_iterator, but this time at the container level ). The
OP function would read like

void print_vector_of_intervals( indirect_view< std::vector<interval*> > const& vci )
{
// ... vci.at(i) would return a const reference to "*vci_.at(i)",
// where vci_ is the wrapped container
}

PS- anybody knows why google groups always add double spacings in post replyes ?

Jonathan Thornburg

unread,
Apr 20, 2013, 5:45:44 PM4/20/13
to

Francis Glassborow <francis.g...@btinternet.com> wrote:
> There is a very real hidden cost in using indirection for placing
> objects in a container (i.e. using pointers of some form rather than
> the actual objects); the loss of locality. In a world of
> multi-level caches, cache thrashing can very seriously degrade
> performance. This can be so serious that copying becomes a viable
> alternative.

Alas, in my real code the objects in question are large and
noncopyable by design (they inherit from boost::noncopyable).
Fortunately, the bulk of the code's computation is done within the
individual objects, which should have excellent cache locality.

--
-- "Jonathan Thornburg [remove -animal to reply]"
<jth...@astro.indiana-zebra.edu>
Dept of Astronomy & IUCSS, Indiana University, Bloomington, Indiana, USA
on sabbatical in Canada starting August 2012
"Washing one's hands of the conflict between the powerful and the
powerless means to side with the powerful, not to be neutral."
-- quote by Freire / poster by Oxfam


Chris Uzdavinis

unread,
May 1, 2013, 1:41:01 AM5/1/13
to
On Thursday, April 18, 2013 9:50:02 PM UTC-5, Jonathan Thornburg wrote:
>
> // investigate passing vector<foo*> to a function expecting vector<const foo*>

If you don't mind making your implementation itself
be a template this is pretty easy to accomplish.
Here's the idea. (I extracted the printing
of a single interval to a separate function.)

void print_interval(interval const * pI)
{
const interval& I = *pI;
cout << "interval " << pI << " = "
<< "[" << I.min() << ", " << I.max() << "]" << "\n";
}

template <typename Interval>
void print_vector_of_intervals(const std::vector<Interval>& vci)
{
for (typename std::vector<Interval>::template const_iterator it =
vci.begin(); it != vci.end(); ++it)
{
if (*it)
print_interval(*it);
}
}


and of course, with c++0x you can use auto to clean that up.

for (auto it = vci.cbegin(); it != vci.cend(); ++it)
...


Chris


--

Jeff Flinn

unread,
May 1, 2013, 11:12:56 AM5/1/13
to
{ Please limit your quoting to the minimum needed to establish context
-mod }
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


--
0 new messages