You might want to think of allowing more than three states, if you're trying to model hardware lines. Here's what Altera uses in its FPGA simulator:
- 1: Strong High (transistor driven to VDD)
- 0: Strong Low (transistor driven to VSS)
- H: Weak High (resistor pullup to VDD)
- L: Weak Low (resistor pulldown to VSS)
- Z: High Impedance (undriven line)
- X: Unknown
- W: Weak Unknown
- U: Uninitialized
- DC: Don't Care
You might not need W, U, and DC. You can ditch H, L, and Z if your buses are always driven.
Verilog uses even more levels for gate-level modeling, with seven drive strengths for each logic level. The additional levels model capacitive effects on signal lines. This is probably more than you need.
EDIT: Since you mentioned vectors of bits, I have to say that, IMHO, you're not going to find such a library in public use and kept up-to-date because 1) there just aren't that many programmers needing such a thing, and 2) even among them, due to the aforementioned options for modeling line levels, there is little compatibility. Boost's tribools can be pressed into service, but they will not be fast, since operations will be element-by-element and storage will not be optimized, but they may be your only choice if someone is allergic to writing an in-house library that does exactly what you need.
For example, say you want a class that represents vectors of bits with four possible levels: 1, 0, X, and Z. First, you have to define equivalent bit patterns for each level (e.g. X=00, Z=01, 0=10, 1=11; X was chosen as the reset state)
For each operation, you have to write out the truth table, preferably in Karnaugh Map form:
op: & | X (00) | Z (01) | 1 (11) | 0 (10)
-------+--------+--------+--------+--------
X (00) | X (00) | X (00) | X (00) | X (00)
-------+--------+--------+--------+--------
Z (01) | X (00) | X (00) | X (00) | X (00)
-------+--------+--------+--------+--------
1 (11) | X (00) | X (00) | 1 (11) | 0 (10)
-------+--------+--------+--------+--------
0 (10) | X (00) | X (00) | 0 (10) | 0 (10)
(Note that X wins a lot. This is true for most operations.)
Then work out the boolean equations from the K-map:
C = A & B
=> C1 = A1 & B1
C0 = A1 & B1 & A0 & B0 = C1 & A0 & B0
Finally, translate that into C++:
template<size_t NBits> BitVector
{private:
enum { NWords = (NBits+31)/32 };
int32_t storage[NWords][2];
public:
BitVector<NBits> operator &(BitVector<NBits>& rhs)
{ BitVector<NBits> result;
for(unsigned k = 0; k < NWords; ++k)
{ int32_t x = storage[k][1] & rhs.storage[k][1]
result.storage[k][1] = x;
result.storage[k][0] = storage[k][0] & rhs.storage[k][0] & x;
}
return result;
}
};
(Note: I haven't tested the code above, so use at your own risk.)
All of this has to be redone if the set of allowed levels changes. This is why these libraries tend to be too specialized to put into a general-use library like Boost.
EDIT2: It just dawned on me that the BitVector template class has one of the few use cases where overloading the comma operator makes sense:
template<size_t NBitsR>
BitVector<NBits+NBitsR> operator ,(const BitVector<NBitsR>& rhs);
This lets you concatenate bit vectors:
BitVector<8> a("1110 0111");
BitVector<4> b("0000");
BitVector<12> c = (a, b); // == BitVector<12>("0000 1110 0111")
... which seems like the most intuitive way to pad one vector up to the size of another (it's easy to show that such padding should not be implicit, ever) or merge vectors together.