Overloading comma to give a concatenation operator: various problems

P

Paul Davis

I'd like to overload 'comma' to define a concatenation operator for
integer-like classes. I've got some first ideas, but I'd appreciate a
sanity check. The concatenation operator needs to so something like
this:

1) e = (a, b, c, d); // concatenate a,b,c,d into e
2) (a, b, c, d) = e; // get the bits of e into a,b,c, and d

For example, in the second case, assume that a,b,c,d represent 2-bit
integers, and e represents an 8-bit integer. If e contains 0b10110100,
then after the assignment, a contains 0b10, b contains 0b11, and so
on.

My current thinking, for the second case, is:

1) Overload comma for the new integer class. This needs several (3?)
prototypes, because the operands may be const temporaries.

2) In the second example, 'a,b' is evaluated first. This results in a
temporary, so I return a const reference to the temporary. This
requires this operator:

const wibble& operator , (wibble&, wibble&) // proto 1

3) '(temporary , c)' is then evaluated. This requires this operator:

const wibble& operator , (const wibble&, wibble&) // proto 2

4) Eventually, we end up with a temporary which has 'e' assigned to
it, which needs this operator:

const wibble& operator= (wibble&) // proto 3
(or just void operator= (wibble&)?)

If this makes sense, my first problem is how to get the value of 'e',
which is only seen by operator=, into the the a, b, c, and d objects.
Any ideas? Currently, my only idea is that the comma operator should
store a reference to its operands in a global list somewhere, and
operator= then runs through the list assigning to all the wibbles in
it. This doesn't feel good, and there's also the question of when to
initialise the list. However, I guess I could do this with the comma
operators which have a non-const left operand, since they should
always be used for the first comma evaluation.

Next question: what if the user wants to do

(a, (b, c), d) = e;

Is this possible? Does the temporary result of (b,c) go out of scope
before it can be used to evaluate (a, (b,c))? If this is Ok,
presumably it would require a third comma operator:

const wibble& operator , (wibble&, const wibble&)

Does any of this make sense?

Many thanks

Paul
______________________________________
To get a valid mail address: s/m@/m1@/
 
V

Victor Bazarov

Paul Davis said:
I'd like to overload 'comma' to define a concatenation operator for
integer-like classes. I've got some first ideas, but I'd appreciate a
sanity check. The concatenation operator needs to so something like
this:

1) e = (a, b, c, d); // concatenate a,b,c,d into e
2) (a, b, c, d) = e; // get the bits of e into a,b,c, and d

For example, in the second case, assume that a,b,c,d represent 2-bit
integers, and e represents an 8-bit integer. If e contains 0b10110100,
then after the assignment, a contains 0b10, b contains 0b11, and so
on.

My current thinking, for the second case, is:

1) Overload comma for the new integer class. This needs several (3?)
prototypes, because the operands may be const temporaries.

2) In the second example, 'a,b' is evaluated first. This results in a
temporary, so I return a const reference to the temporary.

I'd return a value.
This
requires this operator:

const wibble& operator , (wibble&, wibble&) // proto 1

I'd do it

wibble operator ,(wibble&, wibble&);
3) '(temporary , c)' is then evaluated. This requires this operator:

const wibble& operator , (const wibble&, wibble&) // proto 2

Again, return a value instead

wibble operator ,(const wibble&, wibble&)
4) Eventually, we end up with a temporary which has 'e' assigned to
it, which needs this operator:

const wibble& operator= (wibble&) // proto 3
(or just void operator= (wibble&)?)

The default signature of the assignment operator would be

wibble& operator =(const wibble&)

or

wibble& operator =(wibble&)

depending on whether you want to be able to assign from
a temporary or not.
If this makes sense, my first problem is how to get the value of 'e',
which is only seen by operator=, into the the a, b, c, and d objects.
Any ideas? Currently, my only idea is that the comma operator should
store a reference to its operands in a global list somewhere, and
operator= then runs through the list assigning to all the wibbles in
it. This doesn't feel good, and there's also the question of when to
initialise the list. However, I guess I could do this with the comma
operators which have a non-const left operand, since they should
always be used for the first comma evaluation.

Next question: what if the user wants to do

(a, (b, c), d) = e;

Is this possible? Does the temporary result of (b,c) go out of scope
before it can be used to evaluate (a, (b,c))? If this is Ok,
presumably it would require a third comma operator:

const wibble& operator , (wibble&, const wibble&)

Does any of this make sense?

I think what you need is some kind of 'lazy evaluator' class that
would store the values of all those wibbles and then when assigned
to something would evaluate the wibbles and combine them or spread
them around.

class wibble_combiner
{
list<wibble*> candidates;
bool spreading; // should be 'true' by default
list<wibble> values;

public:

wibble_combiner(wibble&); // starts by putting
// this wibble in 'candidates'
// switches 'spreading' on

wibble_combiner(const wibble&); // starts by putting
// this wibble in 'values'
// switches 'spreading' off

wibble_combiner& operator ,(wibble&); // if spreading,
// adds to 'candidates'
// otherwise - to 'values'

wibble_combiner& operator ,(const wibble&); // turns spreading off
// forms 'values' if not formed
// adds the wibble to 'values'

wibble_combiner& operator ,(const wibble_combiner&);

wibble_combiner& operator =(const wibble&); // to spread
// will do nothing or throw
// if 'spreading' is 'false'

operator wibble() const; // conversion -- to combine,
// regardless of 'spreading'
};


How's that? Well, try it, see if you can get anywhere. Post
your results or, if you have any, your questions.

Victor
 
D

David Cattarin

Paul Davis said:
I'd like to overload 'comma' to define a concatenation operator for
integer-like classes. I've got some first ideas, but I'd appreciate a
sanity check.

Well let's see:
1. Don't do it.
2. Read More Effective C++.
3. Don't do it.

Aside from the fact that it will be more confusing to people that
don't realize that you've changed the semantics of the comma operator,
I think you'll also find that it will cause you problems.

Look at it this way, what do you think myFn( a, b, c ) should do?

I'll suggest that you either define a good method name (like concat)
or use the "standard" operator for concatenation: +=.

With a method name like concat you could do:
x = a.concat( b ).concat( c ).concat( d ).concat( e );
IMO, this is clearer in intent than:
x = a, b, c, d, e;

Dave
 
E

Erik

Victor Bazarov said:
I'd return a value.


I'd do it

wibble operator ,(wibble&, wibble&);


Again, return a value instead

wibble operator ,(const wibble&, wibble&)


The default signature of the assignment operator would be

wibble& operator =(const wibble&)

or

wibble& operator =(wibble&)

depending on whether you want to be able to assign from
a temporary or not.


I think what you need is some kind of 'lazy evaluator' class that
would store the values of all those wibbles and then when assigned
to something would evaluate the wibbles and combine them or spread
them around.

class wibble_combiner
{
list<wibble*> candidates;
bool spreading; // should be 'true' by default
list<wibble> values;

public:

wibble_combiner(wibble&); // starts by putting
// this wibble in 'candidates'
// switches 'spreading' on

wibble_combiner(const wibble&); // starts by putting
// this wibble in 'values'
// switches 'spreading' off

wibble_combiner& operator ,(wibble&); // if spreading,
// adds to 'candidates'
// otherwise - to 'values'

wibble_combiner& operator ,(const wibble&); // turns spreading off
// forms 'values' if not formed
// adds the wibble to 'values'

wibble_combiner& operator ,(const wibble_combiner&);

wibble_combiner& operator =(const wibble&); // to spread
// will do nothing or throw
// if 'spreading' is 'false'

operator wibble() const; // conversion -- to combine,
// regardless of 'spreading'
};

Here's my try. Compiles and produces expected results with VC++ 7.0.
Try adding (w1, w2, w5, w4) = l to main and it won't compile (because w5 is
const).
If anyone has a better suggestion, please correct me.

#include <list>
#include <iostream>
#include <climits>

class wibble_combiner;
class const_wibble_combiner;

class wibble {
public:
wibble(unsigned char v) : value(v) {}
const wibble &operator=(unsigned char v) { value = v; return *this; }
operator unsigned char() const { return value; }

wibble_combiner operator,(wibble &);
const_wibble_combiner operator,(const wibble &) const;
wibble_combiner operator,(const wibble_combiner &);
const_wibble_combiner operator,(const const_wibble_combiner &) const;

private:
unsigned char value;
friend class wibble_combiner;
friend class const_wibble_combiner;
};

class const_wibble_combiner {
public:
operator unsigned long() const {
unsigned long l = 0;
for (std::list<const wibble *>::const_iterator I = lst.begin(); I !=
lst.end(); I++)
l = l << CHAR_BIT | (*I)->value;
return l;
}

const_wibble_combiner operator,(const const_wibble_combiner &c) const {
const_wibble_combiner rslt(*this);
rslt.lst.insert(rslt.lst.end(), c.lst.begin(), c.lst.end());
return rslt;
}

const_wibble_combiner operator,(const wibble &w) const {
const_wibble_combiner rslt(*this);
rslt.lst.push_back(&w);
return rslt;
}

private:
explicit const_wibble_combiner(const wibble &w1, const wibble &w2) {
lst.push_back(&w1);
lst.push_back(&w2);
}

const_wibble_combiner(const const_wibble_combiner &c) : lst(c.lst) {}
const_wibble_combiner &operator=(const const_wibble_combiner &c) { lst =
c.lst; }

std::list<const wibble *> lst;

friend class wibble;
friend class wibble_combiner;
};

class wibble_combiner : public const_wibble_combiner {
public:
using const_wibble_combiner::eek:perator,;
using const_wibble_combiner::eek:perator=;

unsigned long operator=(unsigned long l) {
long result = l;
for (std::list<const wibble *>::reverse_iterator I = lst.rbegin(); I !=
lst.rend(); I++) {
(const_cast<wibble *>(*I))->value = (unsigned char)l;
l >>= CHAR_BIT;
}
return result;
}

const wibble_combiner &operator=(const const_wibble_combiner &c) {
std::list<const wibble *>::reverse_iterator I1 = lst.rbegin();
std::list<const wibble *>::const_reverse_iterator I2 = c.lst.rbegin();
for (; I1 != lst.rend() && I2 != c.lst.rend(); I1++, I2++) {
(const_cast<wibble *>(*I1))->value = (*I2)->value;
}
while (I1 != lst.rend())
(const_cast<wibble *>(*I1))->value = 0;

return *this;
}

wibble_combiner operator,(const wibble_combiner &c) const {
wibble_combiner rslt(*this);
rslt.lst.insert(rslt.lst.end(), c.lst.begin(), c.lst.end());
return rslt;
}

wibble_combiner operator,(wibble &w) const {
wibble_combiner rslt(*this);
rslt.lst.push_back(&w);
return rslt;
}

private:
explicit wibble_combiner(wibble &w1, wibble &w2) :
const_wibble_combiner(w1, w2) {}

wibble_combiner(const wibble_combiner &c) : const_wibble_combiner(c) {}

friend class wibble;
};


wibble_combiner wibble::eek:perator,(wibble &w) {
return wibble_combiner(*this, w);
}

const_wibble_combiner wibble::eek:perator,(const wibble &w) const {
return const_wibble_combiner(*this, w);
}

wibble_combiner wibble::eek:perator,(const wibble_combiner &c) {
wibble_combiner rslt(c);
rslt.lst.push_front(this);
return rslt;
}

const_wibble_combiner wibble::eek:perator,(const const_wibble_combiner &c)
const {
const_wibble_combiner rslt(c);
rslt.lst.push_front(this);
return rslt;
}

int main() {
using std::cout;
using std::hex;
using std::endl;

wibble w1(1), w2(2), w3(3), w4(4);
const wibble w5(5), w6(6), w7(7), w8(8);
long l = 0;

l = (w1, w2, w3, w4);
cout << hex << l << endl; l = 0;
l = ((w1, w2), w3, w4);
cout << hex << l << endl; l = 0;
l = (w1, (w2, w3), w4);
cout << hex << l << endl; l = 0;
l = (w1, w2, (w3, w4));
cout << hex << l << endl; l = 0;

l = (w5, w6, w7, w8);
cout << hex << l << endl; l = 0;
l = ((w5, w6), w7, w8);
cout << hex << l << endl; l = 0;
l = (w5, (w6, w7), w8);
cout << hex << l << endl; l = 0;
l = (w5, w6, (w7, w8));
cout << hex << l << endl; l = 0;

l = 0x0a0b0c0d;
(w1, w2, w3, w4) = l;
cout << (int)w1 << " " << (int)w2 << " " << (int)w3 << " " << (int)w4 <<
endl; w1 = 1; w2 = 2; w3 = 3; w4 = 4;
((w1, w2), w3, w4) = l;
cout << (int)w1 << " " << (int)w2 << " " << (int)w3 << " " << (int)w4 <<
endl; w1 = 1; w2 = 2; w3 = 3; w4 = 4;
(w1, (w2, w3), w4) = l;
cout << (int)w1 << " " << (int)w2 << " " << (int)w3 << " " << (int)w4 <<
endl; w1 = 1; w2 = 2; w3 = 3; w4 = 4;
(w1, w2, (w3, w4)) = l;
cout << (int)w1 << " " << (int)w2 << " " << (int)w3 << " " << (int)w4 <<
endl; w1 = 1; w2 = 2; w3 = 3; w4 = 4;

l = (w1, w2, w3, w4) = (w5, w6, w7, w8);
cout << hex << l;

return 0;
}
 
P

Paul Davis

Thanks Victor/Erik - this has been very useful; I've got some working
code now.

Cheers

Paul
 
P

Paul Davis

There's a hidden agenda - the users of this code will be more used to
hardware description languages, where what I described is the standard
'bit concatenation' mechanism.
Well let's see:
1. Don't do it.
2. Read More Effective C++.

Ah, if only. However, I've still got another 2,000 pages to get
through in the 4 books I've already got. I'm not expecting to finish
this side of the grave.

Thanks

Paul
 
D

David Cattarin

Paul Davis said:
There's a hidden agenda - the users of this code will be more used to
hardware description languages, where what I described is the standard
'bit concatenation' mechanism.

Ah... In that case, these people are familiar with this convention.
Out of curiosity, which HDL are you referring to?

One of the strong-points of C++ is that it does allow you to do such
things. This can also backfire if the "new" features cause curious
interactions. In any case, I thought it fair to warn you.
Ah, if only. However, I've still got another 2,000 pages to get
through in the 4 books I've already got. I'm not expecting to finish
this side of the grave.

LOL. I understand that feeling.
Good luck,
Dave
 
P

Paul Davis

Ah... In that case, these people are familiar with this convention.
Out of curiosity, which HDL are you referring to?

The only commercially significant HDLs are Verilog and VHDL. Verilog
concatenation is much the same as I described above:

// X=1'b1, Y=2'b01
Z = {X, Y, 3'b001}; // Z = 6'b101001

The concatention can also appear as an lvalue. VHDL was derived from
Ada, and uses the '&' operator for concatenating any 1D arrays; ie.
"abc" & 'd' = "abcd", b"0001" & b"0101" = b"00010101", etc.

SystemC is a relatively new HDL, and is actually a C++ class library;
it does concatenation exactly as I described above.

Cheers

Paul
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,994
Messages
2,570,223
Members
46,813
Latest member
lawrwtwinkle111

Latest Threads

Top