From: Goran on
On May 30, 1:39 am, M Stefan <mstefa...(a)gmail.com> wrote:
> Hello. I am trying to write a class similar to this:
> template <class StringT>
> struct A
> {
> A (StringT const &str) : str(str) { }
> StringT const &str;};
>
> The problem is that the constructor allows passing temporaries, which
> get destroyed as soon as the constructor finishes executing, not after
> the object is destroyed.
> For instance, constructing the class: A<std::string> a("hello"); would
> cause an implicit conversion from char const* to std::string const&,
> and the converted temporary will be passed to the ctor. The reference
> str of the class is then bound to this temporary, which gets destroyed
> before the object, thus leaving the object with an invalid reference.
> Then, I modified it so that no implicit conversions are allowed:
> template <class StringT>
> struct A
> {
> A (StringT const &str) : str(str) { }
> template <class U> explicit A (U) { BOOST_STATIC_ASSERT(false); }
> StringT const &str;};
>
> This fixes the problem with the conversion from char const* to
> std::string const&, but still allows the following way of constructing
> the class: A<std::string> a( (std::string("a")) );
> This constructs A with a temporary which again gets destroyed before
> the object, thus leaving an invalid ref behind.
> Discussing with some other people, we have come up with several
> solutions, none of which fully satisfy me:
> 1) Make the constructor take a non-const ref as opposed to a const
> ref. (Why not: breaks const correctness)

No.

Example:

TYPE* p;
{
TYPE t;
p = &t;
}
TYPE& r = *p; // ref broken
your_class o(r); // object broken.

> 2) Add an additional level of indirection: (Why not: likely overhead)
> struct A
> {
> boost::any str;
> A () : str() {}
> template <class StringT> void set_str(StringT const &s) { str = s; }};

No. Using any is more overhead than just copying data (could change
with movable types of new standard? perhaps).

> 3) Make the constructor take a StringT as opposed to a reference (Why
> not: overhead and additional memory)

No, no, no! If parameter is a temporary, it will be destroyed when
call finishes, leaving your object with a dangling reference just the
same.

> Please let me know if you have any better solutions to my problem.

In languages like C and C++, there are no, zilch, nada solutions. You
have to handle lifetime of your stuff at any point manually. That's
how the language works, so use it that way. You passed a temporary and
stored a reference to it. That does not work, end of.

Goran.

P.S. Martin B's "solution" isn't, e.g.

class x
{ public:
x(const TYPE* p) : _r(*p) {}
TYPE& _r;
};

is broken with

x(NULL); // bad x._r, no help from compiler

and

x(&TYPE()); // bad x._r, no help from compiler

Goran.


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

From: M Stefan on
I may have omitted a few details which at the beginning have seemed
irrelevant: I am writing some generic string algorithms and the
template
parameter is actually of range concept (Boost.Range). Basically, it
allows any thing with a begin(), an end() and a few other things.
Additionally, my range concept also allows char* and wchar_t*.

After giving it considerable thought, I have managed to come up with a
solution to my problem:

template <class T>
struct My : boost::noncopyable
{
public:
My () : t_(), range_() { }

//a pointer is passed, we are relying on the fact that its
lifetime
//will be bigger than that of the object, so we don't make a copy
//basically we default-construct our t_ (an empty string)
//and initialize our range (COPY AVOIDED)
//! \warning This is a fast constructor, because your string is
not copied.
//! However, in order to use this constructor, you *MUST*
be able
//! to guarantee that the lifetime of the pointee is at
least as long
//! as the lifetime of this object
explicit My (T const *t) : t_(), range_(boost::as_literal(*t)) { }

//a const ref is passed, we cannot rely on its lifetime, so we're
making a copy.
//store the copy in t_, and the range is initialized with t_'s
range
//(COPY MADE)
//! \warning This constructor performs a copy of your string.
//! If you want to avoid copying strings around, use the
ctor taking a
//! pointer.
explicit My (T const &t) : t_(t), range_(boost::as_literal(t_))
{ }

# ifdef BOOST_HAS_RVALUE_REFS
//we have rvalue refs and we were passed a temporary
//we're just gonna move the temporary to t_
//and initialize our range with the range of t_ (COPY AVOIDED)
explicit My (T const &&t) : t_(std::move(t)),
range_(boost::as_literal(t_)) { }
# endif

private:
T t_;
//typename make_properly_copyable<T>::type t_;
boost::iterator_range<typename
boost::range_const_iterator<T>::type> range_;
};

As you can see, the constructors may or may not copy the string into
t_,
but they always properly copy the string's range into range_.
The class' algorithms will never rely on the contents on t_, but only
on the
contents of range_.
In certain cases, t_ may be forced to make a copy of the passed
string,
whereas in other cases a move is made, or t_ is default-constructed.
This obviously assumes that default-constructing is a cheap operation,
much cheaper
than copy constructing a large string.

Here are a few use cases of the class:
//we are passing a temporary, and the class has no choice but to make
a copy
//(or a move if the compiler supports it)
My<std::string> a("copy or move is made");

//we are passing a lvalue reference, the class has no choice but
//to make a copy
std::string s("copy is made");
My<std::string> b(s);

//no copy is made, the range is initialized with the range of s2
std::string s2("copy is not made, but you must make sure the lifetime"
"of this object is long enough");
My<std::string> c(&s2);

As you can see, I've commented out "typename
make_properly_copyable<T>::type t_;".
What this did was it basically checked if T was char*, and if it was,
then it wrapped
it into a class that allowed proper copying using strlen() and
strncpy().

Let me know what do you think about my solution, and thank you for
your answers.

Yours sincerely,
Stefan

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