I would like to know how to implement the
contains
function from the following code in a portable manner.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
#include <stddef.h>
#include <assert.h>
struct A {
int x;
} a;
struct B {
int y;
} b;
bool contains(const void *outer, size_t size, const void *inner);
int main()
{
assert(contains(&a, sizeof(a), &a.x));
assert(contains(&b, sizeof(b), &b.y));
assert(!contains(&a, sizeof(a), &b.y));
assert(!contains(&b, sizeof(b), &a.x));
}
|
It can be used in the GC implementation in the following post:
http://lists.boost.org/Archives/boost/2009/04/150748.php.
I am not affiliated with the development effort. I just explore the code. Tentatively at this point, primarily for educational purposes. I imagine such functionality can be useful for other custom structures. Particularly allocators.
Now the prototypical solution for most practical purposes I believe is:
1 2 3 4 5
|
bool contains(const void *outer, size_t size, const void *inner)
{
return (outer <= inner) &&
(inner < (const char *)outer + size);
}
|
(The C-style cast is for readability in my snippets.)
The problem with the above code is that according to the standard, pointers neither pointing in the same array, nor pointing at instance members of the same object, are not comparable with predictable behavior. To take the imagination to its extreme (beyond rational thought), it may cause system reset or something.
Now, I have already asked a question in another forum regarding portable comparison of pointers, also in reference to their usage in STL. And I was pointed to the fact that the functors from
<functional>
provide total order on all primitive data types, including pointers. Indeed, the case of pointers is even treated exclusively by the standard.
So, the above function can be rewritten like this:
1 2 3 4 5 6 7
|
bool contains(const void *outer, size_t size, const void *inner)
{
return less_equal<const char*>()((const char *)outer,
(const char *)inner) &&
less<const char*>()((const char *)inner,
(const char *)outer + size);
}
|
I use
char*
for a reason. I try to exploit the guarantee provided in the section
Basic concepts/Types (from the standard). It is said, in summary, that the representation of an object
x
must be contained in the
characters in the memory range between
&x
and
(char*)&x + sizeof(x)
. Now, to me this implies, that if
x
has a member
x.y
, then its address (
&x.y
) must also be somewhere in this range. As a matter of fact,
&x.y
to
(char*)&x.y + sizeof(x.y)
must be a sub-range. What do you think of such interpretation?
Anyway, I come to my second issue. How do we interpret the anti-symmetry requirement of
less_equal
in its its total order? I guess it could be something like:
(*) ...
less_equal<...>()(a,b)
and less_equal<...>()(b,a)
implies equal_to<...>()(a,b)
.
This is not saying much on its own. I mean, the equivalence classes of
equal_to
can take any shape, like cause collisions of everything in an STL container. But then, functors are required to be consistent with the built-in relational operators (
<
,
<=
). So in (*),
equal_to<...>()(a,b)
can be roughly approximated with
a == b
.
For pointers though,
a == b
is not always defined. How do we know for sure, that
&a
and
&b
from my snippet at the top are considered distinct according to
equal_to
? If you create a
set<..>
of pointers to objects allocated on the heap, for example, that you want to be compared with respect to their location in memory, how do you know for certain that the comparison will not turn out to be completely non-discriminating?
I know that in practice C++ is not used in such manner without platform knowledge. I am just asking if the portability for the second
contains
function can be judged clearly by the standard in its present condition. And if so, do you think that it is portable or not?
Thanks, and regards