Operator overloading

Can anyone think of some way of making this, without touching main(), output
pointer::operator->() const
A::a()
pointer::operator->()
A::b()

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

template <typename T>
struct pointer{
	T *p;
	pointer<T>(T *p):p(p){}
	const T *operator->() const{
		std::cout <<"pointer::operator->() const\n";
		return this->p;
	}
	T *operator->(){
		std::cout <<"pointer::operator->()\n";
		return this->p;
	}
};

struct A{
	void a() const{ std::cout <<"A::a()\n"; }
	void b()      { std::cout <<"A::b()\n"; }
};

int main(){
	A a;
	pointer<A> p=&a;
	p->a();
	p->b();
	return 0;
}
Is this supposed to be a brain teaser, or...?

Actually, this looks fun.

-Albatross
No, I'm writing a pointer class with COW semantics and was wondering if I could avoid having read() and write() members.
Hmm. Not sure if I could figure out a way to do what it seems you're trying to do at execution time... well, I was going to suggest changing the return type for A::a and A::b, but silly me that won't work.

-Albatross

EDIT: 1313 posts and counting. So much luck...
Last edited on
I don't think it's possible in C++.

The compiler needs to know the type returned by -> before it can know what a() is, and it can't know whether or not -> needs to be const until after it knows whether or not a() is const.
Ok, here's what I have so far:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>

//this should be empty,
//specialize when necessary

template <class T>
struct PtrProxy_T_IFace {};

template <class T, class T_IFace>
struct PtrProxy: public T_IFace
{
    T * p;

    virtual T * get_p() {return p;}
    virtual ~PtrProxy(){}

    PtrProxy(T * ptr)
    {
        p=ptr;
    }
};

template <class T>
struct Pointer
{
    T * p;

    Pointer<T>(T *p):p(p){}

    PtrProxy<T,PtrProxy_T_IFace<T> > * operator->()
    {
        return new PtrProxy<T,PtrProxy_T_IFace<T> >(p);
    }
};

struct A
{
    void a() const { std::cout <<"A::a()\n"; }
    void b()       { std::cout <<"A::b()\n"; }
};

template <>
struct PtrProxy_T_IFace<A>
{
    //this is needed here,
    //but will be overriden by
    //PtrProxy<A>::get_p

    virtual A * get_p() {return 0;}
    virtual ~PtrProxy_T_IFace(){}

    void a()
    {
        std::cout <<"pointer::operator->() const\n";
        get_p()->a();
        delete this;
    }

    void b()
    {
        std::cout <<"pointer::operator->()\n";
        get_p()->b();
        delete this;
    }
};

int main()
{
    A a;
    Pointer<A> p=&a;
    p->a();
    p->b();
    return 0;
}

Good things:

(1) your main is intact
(2) your struct is intact
(3) modification of your pointer class is minimal

Bad things:

(1) you have to provide a wrapper for every member function of your struct
(2) you have to do bad thing #1 for every struct in your program
(3) misc things I'm missing at the moment

EDIT: Ok, PtrProxy::get_p doesn't override PtrProxy_T_Impl::get_p... I'm working on this now...

EDIT 2: Fixed!
Last edited on
Don't bad things #1 and #2 defeat the point of having a COW pointer class in the first place? You might as well just make the contained struct COW. =P
Can anyone think of some way of making this, without touching main(), output
pointer::operator->() const
A::a()
pointer::operator->()
A::b()
?


Easy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

template <typename T>
struct pointer{
	T *p;
	pointer<T>(T *p):p(p){}
	const T *operator->() const{		
		return this->p;
	}
	T *operator->(){
		return this->p;
	}
};

struct A{
	void a() const{ std::cout <<"pointer::operator->() const\n";
                                 std::cout <<"A::a()\n"; }
	void b()      { 
                             std::cout <<"pointer::operator->()\n";
                             std::cout <<"A::b()\n"; 
                          }
};

int main(){
	A a;
	pointer<A> p=&a;
	p->a();
	p->b();
	return 0;
}


Sorry, couldn't resist ;) Very interesting problem though.

--Rollie
Disch wrote:
You might as well just make the contained struct COW

Yes, I know... I guess it would be useful if you didn't have access to the implementation of that struct.
m4ster r0shi: It's an interesting approach, but in my case unusable because A's members have to remain unknown during the definition of the pointer class.

I ended up discarding the COW pointer idea because I realized making it at the same time safe and support all the required operations completely defeated the purpose, which was avoiding unnecessary dynamic allocations for an opaque pointer. It ended up involving even more allocations plus synchronization every time the class' main member is used.

Now I'm looking at the "fast pimpl" idiom and fast fixed-size allocators, and found something that didn't quite make sense to me.
In this book I'm reading, the author describes the following mechanism:
1. Allocate a sizeof(T)*256 buffer (this->data) that will be used to store allocated objects.
2. this->first*sizeof(T) is the first byte of the first unused block within this->data.
3. The first byte of every unused block is an index that multiplied by sizeof(T) is the first byte of the next unused block within this->data, thus forming a linked list of unused blocks.
The author explained his rationale for not using multibyte types for the index thusly:
We run into alignment issues. Imagine you build an allocator for 5-byte blocks. In this case, casting a pointer that points to such a 5-byte block to unsigned int engenders undefined behavior. This is the big problem. A character type has size 1 by definition and does not have any alignment problem because even the pointer to the raw memory points to unsigned char.
The first time I read this I though he was talking about endianness, but when I thought about it later, I realized that, regardless of endianness, a pointer to an integer points to the lowest byte of the integer.
E.g. 0x01020304 ([]=byte pointed to)
Little endian: [04] 03 02 01
Big endian: [01] 02 03 04
So, what's he talking about?
helios wrote:
So, what's he talking about?

I don't know. I got confused there too. After a couple of minutes, I convinced myself that the 'small' problem he's having is big enough to justify the use of unsigned char, and I skipped that part without bothering to understand the 'big' problem...
Last edited on
After thinking about it, all I came up with was that accessing that part as a multibyte value could cross the alignment boundary, harming performance. But worrying about that doesn't make a whole lot of sense. The design doesn't guarantee that allocated blocks will be aligned, which means that using these objects will be on average slower (since it may take up to two memory reads to retrieve a single multibyte datum). The cost of using the first 2+ bytes dwarfs when compared to the cost of using the objects themselves.

It would be possible to guarantee alignment by doing something like
this->block_size=(sizeof(T)+sizeof(int)-1)/sizeof(int)*sizeof(int);
But then allocating single objects of sizes <sizeof(int) becomes wasteful.
Last edited on
Topic archived. No new replies allowed.