Jeroen
#include <iostream>
#include <utility>
int main()
{
std::pair<int,int> intpair=std::make_pair(3,5);
std::cout << intpair.first << "," << intpair.second << std::endl;
/* the following lines lead to a compile error when uncommented...
std::pair<const int,int>* ptr=&intpair;
std::pair<const int,int>* ptr=static_cast<std::pair<const
int,int>*>(&p1);
/*
/* the following lines compile, but is it safe and portable? */
std::pair<const int,int>* ptr=reinterpret_cast<std::pair<const int,
int>*>(&intpair);
ptr->second=7;
// ptr->first=3; // correctly yields a compile time error when
uncommented.
std::cout << intpair.first << "," << intpair.second << std::endl;
return 0;
}
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
No, this is not portable. What you are trying to to relies on the concept of "layout-compatible" types. For non-class types, layout-compatible types must the *same* type, see 3.9 [basic.types] p11:
"If two types T1 and T2 are the same type, then T1 and T2 are layout-compatible types. [ Note: Layout-compatible enumerations are described in 7.2. Layout-compatible standard-layout structs and standardlayout
unions are described in 9.2. �end note ]"
And 9.2 [class.mem] p17:
"Two standard-layout struct (Clause 9) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible
types (3.9)."
But since int and const int are not layout-compatible, the same property extends to pairs of these types.
Finally, your attempt to convert these pointers to structs is described in 9.2 [class.mem] p20:
"A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa."
Here the connection is created between your pointer conversion and layout-compatible standard-layout structs (As of C++0x, std::pair satisfies the requirements of a standard-layout type).
I agree that the current restrictions of layout-compatible types are very strong and could possibly be weakened to support your example, but AFAIK there exists no corresponding core issue about that yet.
HTH & Greetings from Bremen,
- Daniel Kr�gler
A valid aproach...
> However, I do not seem
> to be able to do this without a reinterpret_cast (see the code below).
> Does the standard guarantee that this use of reinterpret_cast is safe
> and portable?
Okay, Daniel already explained why this doesn't work in much more detail
than I could. Some suggestions how to achieve this nonetheless:
1. redesign
You could pass both the first int and a reference to the second int to the
function. Or pass both in and return the second or a different pair instead.
2. reinterpret_cast
Yes, this invokes UB, but practically you can often live with that. You
asked about portability, but I wonder how portable exactly you need
this. If
it's just to the three desktop OSs on "normal" hardware you should be fine.
Of course such a hack deserves a large comment.
3. pair<int, int&>
Firstly, I'm not even sure a reference type is acceptable as template
parameter, so you might end up in proposal 2 effectively. This is also kind
of a redesign, so it could be an intrusive change, but I think this way is
closest to the way you initially wanted to go.
Good luck!
Uli
--
Domino Laser GmbH
Geschäftsführer: Thorsten Föcking, Amtsgericht Hamburg HR B62 932
I think this is an incorrect reading of the standard. In 3.9, there
are several instances where it specifically groups cv-qualified types
with their base types. E.g.
"Scalar types, standard-layout class types (Clause 9), arrays of such
types and cv-qualified versions of
these types (3.9.3) are collectively called standard-layout types."
In 3.9.2:
"Pointers to cv-qualified and cv-unqualified versions (3.9.3) of
layout-compatible
types shall have the same value representation and alignment
requirements"
(Note in above that the "layout-compatible" adjective occurs _inside_
the cv-qualification. I.e. it implies cv-qualifiers do not affect the
layout compatibility of two types.)
Finally, if your interpretation is correct, then you wouldn't be able
to legally write this code:
void layout_compatible_copy(const int& ci, int& nci)
{
memcpy(&nci, &ci, sizeof(nci));
}
Right?
In fact, memcpy() itself would be nonsensical because it by definition
copies const source data to a non-const destination. If those two
types are not layout-compatible simply by reason of cv-qualification,
then how can it do this?
Mike
How coincidental. I just ran into this /exact/ issue a few days ago
while writing my race detector. I didn't find a good solution either.
The problem came up when I tried to write a class that's a drop-in
replacement (more or less) for std::map, but which is implemented by a
sorted std::vector (using std::lower_bound). (I figured that the
number of finds far outweighed the number of inserts, so it seemed
like a good idea at the time.)
The problem comes up when you want to define the iterator. The
iterator method
pair<key_t const, value_t> iterator:: operator-> () const;
works by returning the address of such a pair. You want the first type
of the pair to be const to disallow modification via type safety, but
you want the second type of the pair to be not-const to allow
modification. You also want to allow easy access - that is no copying.
It should be pointing at the pair in the vector. However, you cannot
put pair<key_t const, value_t> in a vector because it is not
assignable, and vectors require assignable types, or at least they did
in C++03. In C++0x, they just need to be moveable or something right?
Still, I suspect that C++0x wouldn't help me here.
I ended up just not defining operator-> for the iterator, and instead
defined methods
key_t const& key() const;
value_t& value() const;
At least if I wanted to go back to the std::map implementation, I
could wrap the std::map::iterator to look like this one, whereas I
cannot wrap the std::map::iterator to look like my iterator. Meh.
You may say that the standard is broken or overcautious, but I don't
think that I'm misreading the standard:
The quoted wording in 3.9 [basic.types] p11 speaks of *same types*, not
of "same types ignoring cv-qualifications". "Same type" is a hard
definition and std::is_same<int, const int>::value evaluates to false
based on the core language definition of "same types".
> In 3.9, there
> are several instances where it specifically groups cv-qualified types
> with their base types. E.g.
>
> "Scalar types, standard-layout class types (Clause 9), arrays of such
> types and cv-qualified versions of
> these types (3.9.3) are collectively called standard-layout types."
This does only say that int and const int are standard-layout types, but
it does not say that int and const int are layout-compatible. It doesn't
matter whether this definition defines these type families by using the
all-embracing notion of "cv-qualified versions of these types".
> In 3.9.2:
> "Pointers to cv-qualified and cv-unqualified versions (3.9.3) of
> layout-compatible
> types shall have the same value representation and alignment
> requirements"
>
> (Note in above that the "layout-compatible" adjective occurs _inside_
> the cv-qualification. I.e. it implies cv-qualifiers do not affect the
> layout compatibility of two types.)
This is an important property for pointers, but it does not allow you to
extend to the assumption that pair<int, int> is layout-compatible to
pair<const int, int>. It does not even say that pair<int, int>* has the
same value representation as pair<const int, int>* because that
assumption would base on the assumption that pair<int, int> is
layout-compatible to pair<const int, int> which is excluded by the
current definition.
> Finally, if your interpretation is correct, then you wouldn't be able
> to legally write this code:
>
> void layout_compatible_copy(const int& ci, int& nci)
> {
> memcpy(&nci,&ci, sizeof(nci));
> }
>
> Right?
>
> In fact, memcpy() itself would be nonsensical because it by definition
> copies const source data to a non-const destination. If those two
> types are not layout-compatible simply by reason of cv-qualification,
> then how can it do this?
memcpy does *not* rely on layout-compatibility, it does rely on trivial
copyability. Trivial copyability is defined in terms of memcpy, so you
cannot proof from this how layout-compatibility is defined.
I agree with Ulrich that the OP example may just work in most
implementations, but it is still undefined behaviour by the standard.
Greetings from Bremen,
Daniel Kr�gler
> /* the following lines compile, but is it safe and portable? */
> std::pair<const int,int>* ptr=reinterpret_cast<std::pair<const int,
> int>*>(&intpair);
> ptr->second=7;
Whether this is layout-compatible or not is irrelevant, since this is
a violation of the strict aliasing rules.
I think you are making an aggressively unfriendly reading of the Standard (note that qualification of 'reading' is not a characterization of the writer nor of the writer's motivation.) As an int variable can be bound to a const int & parameter it would require a degree of perversion from an implementer to make int and const int not layout compatible.
I am morally certain that there is no implementation anywhere that makes a const T and T not layout compatible.
If you believe that this incompatibility is permitted by the Standard I think you should raise a defect report because I am also certain that the overwhelming majority of programmers believe (correctly IMHO) that and int can always be used where a const int is required without any enabling action.
Furthermore, IIRC, cv qualification is a compile time property of code that guides the generation of object code but is not visible in the resulting code.
Finally consider smart pointers, are you claiming that a smart pointer to a const T will generate UB if it points to an unqualified T?
To sum it up. I think that layout compatibility between various CV qualifications (including no qualification) of T is implicit in the Standard even if it is nowhere made explicit.
Francis
Okay, perhaps I am not properly grasping the importance of the
distinction between layout-compatibility and "trivial copyability". I
would've thought that "trivial copyability" implied layout-
compatibility.
Can you provide a plausible example where two things are (according to
the standard) "trivially copyable" yet not layout-compatible without
involving cv-qualifiers?
Honestly, I have difficulty imagining such a thing...
Mike
(Why are you passing a pointer? I am asking because it's suspect in C+
+ code, more often that not. If you pass a pointer to a C++ function
from C++ code, you are expected to handle NULL in the callee. If you
aren't doing that, you should be using a reference).
That said, casting won't get you there, I don't think. So how about
changing the function signature to accept your_pair.first by value or
const reference, and passing another by reference?
If you don't like that, how about passing a proxy object to the
function that can do what you need? e.g.
template<typename first, typename second>
class can_only_modify_second
{
private:
std::pair<first, second>& data_;
public:
can_only_modify_second(std::pair<first, second>& data) : data_(data)
{}
std::pair<first, second>& get_data() { return data_; };
void modify_second(const second& value) { data_.second = value; }
};
then change your function to work with can_only_modify_second in lieu
of pair<>.
By the way, in OOP theory/evangelism/best practices/pick your
buzzword, certain Robert C. Martin abstracted above as "interface
segregation principle". Effectively, he says: between pieces of code,
try using client-specific interfaces. Seems to do it for you (at least
from what you have described so far).
Goran.
Goran.
My application is basically the same as Joshua's. The function taking
the pointer is a constructor for an iterator for a map-like container.
I suppose I could construct the iterator with a const pointer to
pair.first and a non-const pointer to pair.second. However, then I
need to get tricky with the iterator increment and decrement
operators, since I can no longer use normal pointer arithmetic. If the
required pointer arithmetic could be performed safely and portably,
that would be a good way to go. Need to think about that a bit more.
For the moment, I have used the reinterpret_cast hack (with a large
comment as suggested by Ulrich), as this works on my platform &
compiler. This is a hobby project, so no-one is going to shoot me for
writing code with undefined behaviour. However, I'm also using it to
teach myself to write robust code, hence my "obsession" with standard-
conforming portable code.
The easy solution would be to store a reference to the container
within the iterator and delegate iterator increment/decrement to the
underlying container. However, the reason I'm implementing this
container is that std::map proved to be a performance bottleneck in my
application (I measured). This solution would result in slower code.
My application requires only unsigned ints over a limited range (a few
thousand at most) as a key-type. My map-like container takes advantage
of that by sacrificing some additional storage speed by using three
std::vectors, one of which is used as a bitmask indicating container
membership, the second is used to store the actual values
(std::pair<key,mapped>) and the 3rd is used to store an index into the
second by key. Using this data structure, map insert, find and erase
all become O(1) at the cost of additional storage and limiting the the
key-type to some kind of integers (or types that can be implicitly
converted to integers).
I ran into this problem when trying to implement iterators for this
map. const_iterators are of course no problem whatsoever.
{ trailing quotation removed -mod }
I consider volatile-qualifiers as special beasts, and a similar problem
in regard to volatile-qualifications and trivial copyability, see e.g.
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#496
And no, I have no particular examples to present. I have only quoted the
standard and it seems to contradict to most peoples expectations,
therefore I have forwarded this question to the core working group.
I notice that the C1x standard (N1569) is pretty similar here, but adds
somewhat more in regard to qualifiers:
6.2.7:
"Two types have compatible type if their types are the same. Additional
rules for determining whether two types are compatible are described in
6.7.2 for type specifiers, in 6.7.3 for type qualifiers,[..]"
6.7.3 p10:
"For two qualified types to be compatible, both shall have the
identically qualified version of a compatible type; the order of type
qualifiers within a list of specifiers or qualifiers does not affect the
specified type."
The last part seems at least not to exclude the possibility that const
int and int are compatible types. It might be very reasonable that C++
follows this approach.
Greetings from Bremen,
- Daniel Kr�gler
I have been told that I'm misinterpreting the C standard here, in fact
the C term of "compatible types" corresponds (more or less) to the C++
"same type" concept. I also misinterpreted 6.7.3 p10 to allow for making
int and const int compatible, because the absense of a qualifier is also
a difference.
Nevertheless it seems that C++ misses to synchronize the term
"layout-compatible types" with the "same representation and alignment
requirements" properly taking properly type qualification into account.
I apologize for any confusion && send greetings from Bremen,
Ah, you have a sequence of these going in then? With additional "last"
or "count"?
OK then, no way around that, sorry for being a pain. No chance of
getting an iterator in?
Goran.
Does that mean that the reinterpret_cast will/should work after all?