value_initialized, deep const, and template instantiation

value_initialized has non-deep (shallow?) const semantics. const value_initialized<T> is supposed to be const. Unfortunately, due to compiler errors in VC++ and Borland, it has operator T &() const instead of operator T&() operator const T&() const which breaks const-correctness in a big way. I'd like for there to be a config macro that identifies this error. I would make one, as well as a test for it, myself, but I only have access to icc and gcc on linux, both of which pass the test, so it's hard trying to identify the extent of the overloading selection failure. After creating this macro, we could use it to make a const correct value_initialized class for users with more advanced compilers. For those compilers, I think there would be value to adding operator-> for the invocation of member functions, like boost::optional. As an alternative, deep-copy would break const expectations in generic functions that take a const T &, but it would preserve it elsewhere, so it may be worth some more thought. Finally, I have come up with a similar class (which I wrote with deep const sematics, although I am open to argument about shallow const) for solving the following problem: ---------------------------------------------------------------------- #include <iostream> template<typename T> struct wrap { static const T joe; }; template<typename T> const T wrap<T>::joe; struct call_out { int num; call_out() : num(77) {} }; template<typename T> struct check_call { check_call() { std::cout << wrap<T>::joe.num << std::endl; } }; check_call<call_out> x; int main(){} ----------------------------------------------------------------------- This outputs 0, not 77, on gcc 3.3.2, intel 8, and vc6. I don't know if that's correct, 14.7.1/1 says: "... in particular, the initialization (and any associated side effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist." If this is std-conforming behavior, or not, i have a workaround that allows: ------------------------------------------------------------------------ #include <iostream> #include "can_init.hpp" template<typename T> struct wrap { static can_init<const T> joe; }; template<typename T> can_init<const T> wrap<T>::joe; struct call_out { int num; call_out() : num(77) {} }; template<typename T> struct check_call { check_call() { wrap<T>::joe.ensure_init(); std::cout << wrap<T>::joe->num << std::endl; } }; check_call<call_out> x; int main(){} ----------------------------------------------------------------------- which obviously uses deep const. Is there any interest in including this in boost/utility/? Should it have shallow or deep const semantics? Jim

Hi Him,
value_initialized has non-deep (shallow?) const semantics. const value_initialized<T> is supposed to be const. Unfortunately, due to compiler errors in VC++ and Borland, it has
operator T &() const
instead of
operator T&() operator const T&() const
which breaks const-correctness in a big way.
I'd like for there to be a config macro that identifies this error.
[snip]
Deep const semantics are definitely needed in value_init<>. However, using a configuration macro to select the correct version for non-broken compilers was considered and rejected in order to avoid having two different compiler-dependent versions of the same utility. It would be very confusing if constantness semantics were to change depending on the compiler. This situation is unlike those cases were an interface has additional methods enabled for better compilers, because in that case, the additional methods are unavailable on lesser compiler and so there is no behavioral ambiguity.
For those compilers, I think there would be value to adding operator-> for the invocation of member functions, like boost::optional.
I'm not sure. operator-> generated a long discussion because of its implied pointer semantics. In the case of boost::optional<>, the possibility of the uninitialized state is my corner argument in favot of it. Yet with value_initialized<> I can't see any favorable argument. FWIW, both the deep-const and the member access issues are solved by the recommended interface: value_initialized<X> const cx ; get(cx).foo();
As an alternative, deep-copy would break const expectations in generic functions that take a const T &, but it would preserve it elsewhere, so it may be worth some more thought.
Yes, though value_initalized is a value wrapper, so it must have deep-copy.
Finally, I have come up with a similar class (which I wrote with deep const sematics, although I am open to argument about shallow const) for solving the following problem:
---------------------------------------------------------------------- #include <iostream>
template<typename T> struct wrap { static const T joe; };
template<typename T> const T wrap<T>::joe;
struct call_out { int num; call_out() : num(77) {} };
template<typename T> struct check_call { check_call() { std::cout << wrap<T>::joe.num << std::endl; } };
check_call<call_out> x;
int main(){} -----------------------------------------------------------------------
This outputs 0, not 77, on gcc 3.3.2, intel 8, and vc6. I don't know if that's correct, 14.7.1/1 says:
"... in particular, the initialization (and any associated side effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist."
Interesting.... don't know if it is comforming or not.
If this is std-conforming behavior, or not, i have a workaround that allows: ------------------------------------------------------------------------ #include <iostream> #include "can_init.hpp"
template<typename T> struct wrap { static can_init<const T> joe; };
template<typename T> can_init<const T> wrap<T>::joe;
struct call_out { int num; call_out() : num(77) {} };
template<typename T> struct check_call { check_call() { wrap<T>::joe.ensure_init(); std::cout << wrap<T>::joe->num << std::endl; } };
check_call<call_out> x;
int main(){} ----------------------------------------------------------------------- which obviously uses deep const. Is there any interest in including this in boost/utility/?
If you can guarantee proper initialization for any compiler were it is available, I'd say yes, I'm interested.
Should it have shallow or deep const semantics?
Deep const, as it is a value wrapper (and not a pointer wrapper) HTH Fernando Cacciola SciSoft

I was a little unclear last time: by deep const I mean something<const T> is the correct indication of const, whereas by shallow const I mean const comething<T> is the correct indication of const. Fernando Cacciola wrote:
It would be very confusing if constantness semantics were to change depending on the compiler.
That's true. Broken const semantics are a problem though, especially the conversion operator. What if the working compilers had the member functions (data and operator T &, perhaps operator->) and non working compilers only had get?
For those compilers, I think there would be value to adding operator-> for the invocation of member functions, like boost::optional.
I'm not sure. operator-> generated a long discussion because of its implied pointer semantics. In the case of boost::optional<>, the possibility of the uninitialized state is my corner argument in favot of it. Yet with value_initialized<> I can't see any favorable argument.
I suppose it depends on your POV. I find get(x).f() and x.data().f() much less clear than x->f(). The reason I find the latter more clear is that it is clear that f is a member function in x's type. get(x) required me to find the get by human-driven ADL, and x.data() looks like I'm acting on a part of x.
As an alternative, deep-copy would break const expectations in generic functions that take a const T &, but it would preserve it elsewhere, so it may be worth some more thought.
Yes, though value_initalized is a value wrapper, so it must have deep-copy.
should have been "deep const". To rehash, that means deep const semantics do the wrong thing in template<typename T> void g(const T & a) {/*whatever*/} but can have the member functions, including conversion, without any worries about older compilers screaming ambiguity.
If you can guarantee proper initialization for any compiler were it is available, I'd say yes, I'm interested.
I will need someone else to do some testing for me. I'll post a candidate soon.
Should it have shallow or deep const semantics?
Deep const, as it is a value wrapper (and not a pointer wrapper)
Huh?

Hi Jim,
I was a little unclear last time: by deep const I mean something<const T> is the correct indication of const, whereas by shallow const I mean const comething<T> is the correct indication of const.
I see. AFAIK, "deep" X means that when an outer object has property X, its inner objects must have X too; while "shallow" X means that the X property of the outer object is not promoted to the inner objects. For example, deep-const for a wrapper W<T> implies that: char const* test ( T const& ) { return 'const' ; } char const* test ( T& ) { return 'mutable' ; } W<T> w; W<T> const wc; test(w); test(wc); should output: const mutable While "shallow-const" implies that the output is: mutable mutable OTOH, *I* call "preserving" X when the value of X for the inner object is independent of the value of X for the outer object. Most template wrappers are preserving in this sense because cv-qualifiers are preserved. For example, preserving-const implies that: W<T> w; W<T const> cw; test(w); test(cw); outputs: mutable const A deep-const wrapper with preserving-const will be such that: W<T> w; W<T> const wc; W<T const> cw; W<T const> const cwc; test(w); test(wc); test(cw); test(cwc); outputs: mutable const const const value_initialized<> is a shallow-preserving-const wrapper: value_initialized<T> w; value_initialized<T> const wc; value_initialized<T const> cw; value_initialized<T const> const cwc; test(w); test(wc); test(cw); test(cwc); outputs: mutable mutable const const Is this the behaviour you want?
Fernando Cacciola wrote:
It would be very confusing if constantness semantics were to change depending on the compiler.
That's true. Broken const semantics are a problem though, especially the conversion operator. What if the working compilers had the member functions (data and operator T &, perhaps operator->) and non working compilers only had get?
This could be done, yes.
For those compilers, I think there would be value to adding operator-> for the invocation of member functions, like boost::optional.
I'm not sure. operator-> generated a long discussion because of its implied pointer semantics. In the case of boost::optional<>, the possibility of the uninitialized state is my corner argument in favot of it. Yet with value_initialized<> I can't see any favorable argument.
I suppose it depends on your POV. I find get(x).f() and x.data().f() much less clear than x->f(). The reason I find the latter more clear is that it is clear that f is a member function in x's type. get(x) required me to find the get by human-driven ADL, and x.data() looks like I'm acting on a part of x.
It's definitely a POV issue. I personally am not so troubled with operator->, but I know my boosters :-) OTOH, get(w) is a very common idiom used by lots of wrappers and, while "x.data()" looks like acting on a part of x, which is exactly what it's doing, "x.data().foo()" OTOH definitely looks like acting on X's data, not X itself. Anyway, data() should be renamed to get() for uniformity with all the other wrappers.
As an alternative, deep-copy would break const expectations in generic functions that take a const T &, but it would preserve it elsewhere, so it may be worth some more thought.
Yes, though value_initalized is a value wrapper, so it must have deep-copy.
should have been "deep const". To rehash, that means deep const semantics do the wrong thing in
template<typename T> void g(const T & a) {/*whatever*/}
Whether its does the wrong thing or not depends on the nature of the wrapper. In general, value-wrappers should be deep-const, and that will do the right thing because T, being a value-wrapper for U, is intended to behave most like U.
but can have the member functions, including conversion, without any worries about older compilers screaming ambiguity.
If you can guarantee proper initialization for any compiler were it is available, I'd say yes, I'm interested.
I will need someone else to do some testing for me. I'll post a candidate soon.
Great
Should it have shallow or deep const semantics?
Deep const, as it is a value wrapper (and not a pointer wrapper)
Huh?
Most value-wrappers are used to extend/shrink an object's interface, or to better define some part of its behavior. In this case, the rest of the wrapped object should be promoted as is to the wrapper so that usage is transparent. In your case, you are fixing an initialization problem, so the wrapper must look like that wrapped in every other respect. This requires deep semantics (const and copy) Fernando Cacciola SciSoft

Fernando Cacciola wrote:
A deep-const wrapper with preserving-const will be such that: [snip] value_initialized<> is a shallow-preserving-const wrapper: [snip] Is this the behaviour you want?
Either one looks fine to me. I am concerned about the present hole, however, where const value_initialized<T> a has a user-defined conversion operator to T &. [snip stuff I agree with]
but can have the member functions, including conversion, without any worries about older compilers screaming ambiguity.
So, I was wrong. Compilers with EDG say: -------------------------------------------------------------- struct A; //typedef int A; struct B { operator const A & (); }; void g(const A &); void g(A &); int main() { g(B()); } ---------------------------------------------------------------- Fails with a claim of ambiguity, but succeeds when line 1 is commented out and line 2 is commented in. So I have to think about that some more. There's also the though of whether to provide template conversion functions - it allows any allowed conversions, but creates ambiguity in ---------------------------------------------------------------------- struct A; struct bar { bar(A); }; can_init<A> z; bar x(z); --------------------------------------------------------------------- Jim

Fernando Cacciola wrote:
A deep-const wrapper with preserving-const will be such that:
W<T> w; W<T> const wc; W<T const> cw; W<T const> const cwc;
test(w); test(wc); test(cw); test(cwc);
outputs:
mutable const const const
value_initialized<> is a shallow-preserving-const wrapper:
value_initialized<T> w; value_initialized<T> const wc;
value_initialized<T const> cw; value_initialized<T const> const cwc;
test(w); test(wc); test(cw); test(cwc);
outputs:
mutable mutable const const
Is this the behaviour you want?
On second thought, I think I like the first better - a user who has written const should expect const - no matter where it was written. There are some notes on the isea of value wrapping, as well as a prototype for can_init at http://japple.freeshell.org/value_wrap.html Jim
participants (2)
-
Fernando Cacciola
-
Jim Apple