views:

297

answers:

7

I am trying to create a bit-vector class in C++ to model some hardware. In most HDLs (hardware description langauges) that I know, specific bits are referenced like this:

my_vector[bit_position]

and sub-vectors are referenced like this:

my_vector[msb:lsb]

or

my_vector[msb,lsb]

I want to be able to do something similar with my bit-vector class. Is there any way to tell operator[] to accept two arguments?

The alternatives I've considered are:

  1. using a range method:

    my_vector.range(msb,lsb)

  2. using a string and parsing it:

    my_vector["msb:lsb"]

But neither of them is attractive. The first, because it is too different from the way it's modeled in HDL, the second because I don't like dealing with strings when I don't have to, and it seems inelegant.

What's the best way to do this?

+17  A: 

a simple struct with two members as a parameter to operator[]...

struct pos
{
  int lsb;
  int msb;
};

pos foo={1,2};

my_vector[foo];

or in the new standard, I believe you can simply do:

my_vector[pos{1,2}]
Nim
Even with the old standard, you can give `pos` a constructor and write `my_vector[pos(1,2)]`, which isn't too bad. In this case it should be called `range`, not `pos`, since we're extracting a sub-range, not addressing in 2 dimensions.
Steve Jessop
This is actually very elegant!
Nathan Fellman
+1 I like this proposition.
Stephane Rolland
Yes, similar to what I was going to suggest simply using a std::pair. +1
Don Wakefield
@Steve Jessop, I was going to call it foo! :) but range is more appropriate!
Nim
+4  A: 

The usual solution is to override the () operator. This lets you do things like my_vector(1, 2).

Work arounds using the [] operator are possible, but as Matthieu M. points out, you need a custom type to be involved.

Eric
Won't work unless "1" is of a type you can add an operator,() to. Which it isn't.
Eric Towers
I like your second alternative … although I’m not convinced that it works.
Konrad Rudolph
This is always an amusing read about how you can abuse C++ to achieve your own ends: http://www.xs4all.nl/~weegen/eelis/analogliterals.xhtml
Eric
@Eric Towers: I would do it as follows. In the range class, override the casting operator so that a range can be implicitly created from an integer. You would need to be able to construct a "partial" range for this (i.e. only one element). Then override the comma operator to accept (int, range) and return a "completed" partial range.
Eric
@Eric: does that actually work, though? I haven't tried it, but do you get the route with the conversion and the call to the overloaded `operator,`, when `1,2` is already a perfectly good expression?
Steve Jessop
@Steve: I'd really like to know about it too, always thought I had to use a "better" type than a built-in. Never really looked into it though, since I am in favor of strong typing and I would typically have a `HigherBit` and `LowerBit` class here, just to make sure I don't mix them up.
Matthieu M.
@Steve Jessop: It should. Matthieu M. has more details in his answer. The problem with just ``1,2'' is that the comma operator eats the first operand and returns just the second so that without the cast/comma overloads in place [1,2] is equivalent to [2].
Eric
@Eric: Won't work. int is an internal type so type inference will stop at int and not attempt to find a user-defined cast that will get the job done.
Eric Towers
@Eric: not if there’s no overload for `operator [](int)`. Then it *must* find a conversion. However, I fear that the sequence point will be evaluated *before* that (this should happen) and only the value of the sequence point (i.e. `2` in the example) will be passed to the `range` class constructor.
Konrad Rudolph
I stand corrected!
Eric
@Konrad: As you say. This won't work with built-ins.
sbi
A: 

Try not to use std::vector<bool>. There is std::bitset which is static and boost::dynamic_bitset which are better.

graham.reeds
How is this an answer to the question?
MSalters
Maybe he is looking two hard at a particular solution to see alternatives.
graham.reeds
To the best of my knowledge, I can do neither arithmetic nor logic operations on `bitset` or `dynamic_bitset`. These are hard requirements for modeling a bit-array in hardware.
Nathan Fellman
@Nathan: Actually `bitset` does support logic operations, `vector<bool>` doesn't, and neither does arithmetic. You can freely translate between `bitset` and `unsigned long` though which gets you closer. Anyway I agree this answer is out of line.
Potatoswatter
@potatoswatter: it's actually not out of line, just not helpful :-)
Nathan Fellman
+2  A: 

Two argument operator[] is not possible in C++. The names of, precedence of, associativity of, and arity of operators is fixed by the language.1 Operator() however can take two arguments...

Eric Towers
+4  A: 

Is there any way to tell operator[] to accept two arguments?

No.

There's two common alternatives. One is to overload operator() instead. You then have to invoke it using the () syntax.

The other one is to have operator[] return a proxy object for which operator[] is overloaded, too. This can be invoked like multi-dimensional arrays in C, with several [][] in series.

sbi
I think that the multi-dimensional array syntax is a bit rubbish in this case, where we're actually after a sub-range. `myvector[0][8]` doesn't obviously mean, "the first 8 bits of `myvector`" as the questioner wants, so a function would almost certainly be better.
Steve Jessop
@Steve. This is certainly true. (I just answered the question as quoted, and obviously forgot about the context.) That's the reason I up-voted [Nim's answer](http://stackoverflow.com/questions/3977817/how-can-i-override-to-accept-two-arguments-in-c/3977854#3977854) instead.
sbi
+19  A: 

The issue:

Apart from operator() all operators have a fixed arity, which effectively precludes any kind of change

You then have several solutions:

  • overload operator() instead: vector(msb, lsb)
  • use two successive invocations: vector[msb][lsb]
  • overload the comma operator: vector[msb,lsb]

The last solution matches the syntax you require, but is somewhat subtle:

  • you first need either msb or lsb to be of a custom type (for operators cannot be overloaded on built-ins only)
  • you then provide an overload of operator, for this type, returning a Range object
  • you finally provide a custom operator[](Range) on your class

The real bummer is the first point: that one of msb or lsb need be of a custom type. This can be somewhat alleviated using Boost.StrongTypedef which creates a custom type that mimicks an existing one.

Matthieu M.
Overloading the comma operator... that's extremely unusual, and likely to trip people up. See also point 20 of http://www.parashift.com/c++-faq-lite/operator-overloading.html#faq-13.9
user9876
@user9876: indeed it is unusual, I mentioned it for completeness though. I would personally use the second solution (successive invocations) since it allows me to "cache" the proxy object in tight loops.
Matthieu M.
@user9876: In this case it's probably less confusing, since it's only used as part of a special usage. Of course, if somebody uses a comma operator in one of the parts of a `for` statement, it's confusion time.
David Thornley
If you want to override comma, you could also use `#define _r (range_intro)` with `my_bits[_r 25, 32 ]` or the like. The cast turns the first integer into your custom type, the nomenclature is legal but unlikely to conflict with anything, and it's a hack, but it properly *looks* like a hack, yet unobtrusively.
Potatoswatter
@Potatoswatter: I am in the habit of using macros with names like `PROJECT_FILE_NAME` to avoid conflict, so I guess the define would not work :) As said, using a custom type (or converting to) would work, however the is always the risk of a user using the comma "normally" and being utterly confused as a result. I think we all agree that both `()` and `[][]` are clearer and should be preferred.
Matthieu M.
Yes… it's worth noting, though, that the most common usage of the comma is to separate function arguments, which is exactly what happens when you overload it here. I personally don't like `()` for subscripting, and `[][]` means something else entirely. Consider the interaction with `operator[](int)` used to access single bits.
Potatoswatter
Can `operator()` be overloaded to accept any number of arguments?
Nathan Fellman
@Nathan: yes it can, which is why this is the usual recommendation.
Matthieu M.
@Potatoswatter: I agree, C++ thrive on ambiguities and using the comma for multiple roles: function argument separator, sequence point and possibly operator; is what prevents us from having a non-hacky syntax :/
Matthieu M.
A: 

use a language with array slices instead? =P

jon_darkstar
that would be an option if the codebase I'm working on weren't in C++
Nathan Fellman
C++ has array slices as `std::slice`, they just don't work very well :vP
Potatoswatter