mirror of
https://github.com/Z3Prover/z3
synced 2025-06-27 08:28:44 +00:00
more ddnf
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
parent
b596828d23
commit
34aa06b5a3
3 changed files with 199 additions and 135 deletions
|
@ -93,6 +93,10 @@ namespace datalog {
|
||||||
return bit_vector::operator==(other);
|
return bit_vector::operator==(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned get_hash() const {
|
||||||
|
return bit_vector::get_hash();
|
||||||
|
}
|
||||||
|
|
||||||
void resize(unsigned n, unsigned val) {
|
void resize(unsigned n, unsigned val) {
|
||||||
while (size() < n) {
|
while (size() < n) {
|
||||||
bit_vector::push_back((val & 2) != 0);
|
bit_vector::push_back((val & 2) != 0);
|
||||||
|
@ -100,8 +104,6 @@ namespace datalog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() const { return false; } // TBD
|
|
||||||
|
|
||||||
bool is_subset(tbv const& other) const {
|
bool is_subset(tbv const& other) const {
|
||||||
SASSERT(size() == other.size());
|
SASSERT(size() == other.size());
|
||||||
return other.contains(*this);
|
return other.contains(*this);
|
||||||
|
@ -135,6 +137,8 @@ namespace datalog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
friend bool intersect(tbv const& a, tbv const& b, tbv& result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set(unsigned index, unsigned value) {
|
void set(unsigned index, unsigned value) {
|
||||||
SASSERT(value <= 3);
|
SASSERT(value <= 3);
|
||||||
|
@ -146,16 +150,29 @@ namespace datalog {
|
||||||
index *= 2;
|
index *= 2;
|
||||||
return (bit_vector::get(index) << 1) | (unsigned)bit_vector::get(index+1);
|
return (bit_vector::get(index) << 1) | (unsigned)bit_vector::get(index+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool intersect(tbv const& a, tbv const& b, tbv& result) {
|
||||||
|
result = a;
|
||||||
|
result &= b;
|
||||||
|
for (unsigned i = 0; i < result.size(); ++i) {
|
||||||
|
if (result.get(i) == BIT_z) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
class dot {
|
class dot {
|
||||||
tbv m_pos;
|
tbv m_pos;
|
||||||
vector<tbv> m_negs;
|
vector<tbv> m_negs;
|
||||||
public:
|
public:
|
||||||
|
dot() {}
|
||||||
dot(tbv const& pos, vector<tbv> const& negs):
|
dot(tbv const& pos, vector<tbv> const& negs):
|
||||||
m_pos(pos), m_negs(negs) {}
|
m_pos(pos), m_negs(negs) {}
|
||||||
|
|
||||||
|
tbv const& pos() const { return m_pos; }
|
||||||
|
tbv neg(unsigned i) const { return m_negs[i]; }
|
||||||
|
unsigned size() const { return m_negs.size(); }
|
||||||
|
|
||||||
bool operator==(dot const& other) const {
|
bool operator==(dot const& other) const {
|
||||||
if (m_pos != other.m_pos) return false;
|
if (m_pos != other.m_pos) return false;
|
||||||
if (m_negs.size() != other.m_negs.size()) return false;
|
if (m_negs.size() != other.m_negs.size()) return false;
|
||||||
|
@ -164,6 +181,7 @@ namespace datalog {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void display(std::ostream& out) {
|
void display(std::ostream& out) {
|
||||||
out << "<";
|
out << "<";
|
||||||
m_pos.display(out);
|
m_pos.display(out);
|
||||||
|
@ -174,6 +192,28 @@ namespace datalog {
|
||||||
}
|
}
|
||||||
out << ">";
|
out << ">";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct eq {
|
||||||
|
bool operator()(dot const& d1, dot const& d2) const {
|
||||||
|
if (d1.pos() != d2.pos()) return false;
|
||||||
|
if (d1.size() != d2.size()) return false;
|
||||||
|
for (unsigned i = 0; i < d1.size(); ++i) {
|
||||||
|
if (d1.neg(i) != d2.neg(i)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hash {
|
||||||
|
unsigned operator()(dot const& d) const {
|
||||||
|
unsigned h = d.pos().get_hash();
|
||||||
|
for (unsigned i = 0; i < d.size(); ++i) {
|
||||||
|
h ^= d.neg(i).get_hash();
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ddnf_mgr;
|
class ddnf_mgr;
|
||||||
|
@ -186,28 +226,53 @@ namespace datalog {
|
||||||
vector<dot> m_pos;
|
vector<dot> m_pos;
|
||||||
vector<dot> m_neg;
|
vector<dot> m_neg;
|
||||||
unsigned m_refs;
|
unsigned m_refs;
|
||||||
|
unsigned m_id;
|
||||||
|
|
||||||
|
friend class ddnf_mgr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ddnf_node(ddnf_mgr& m, tbv const& tbv):
|
ddnf_node(ddnf_mgr& m, tbv const& tbv, unsigned id):
|
||||||
m_tbv(tbv),
|
m_tbv(tbv),
|
||||||
m_children(m),
|
m_children(m),
|
||||||
m_refs(0) {
|
m_refs(0),
|
||||||
|
m_id(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~ddnf_node() {}
|
||||||
|
|
||||||
unsigned inc_ref() {
|
unsigned inc_ref() {
|
||||||
return ++m_refs;
|
return ++m_refs;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned dec_ref() {
|
void dec_ref() {
|
||||||
SASSERT(m_refs > 0);
|
SASSERT(m_refs > 0);
|
||||||
return --m_refs;
|
--m_refs;
|
||||||
|
if (m_refs == 0) {
|
||||||
|
dealloc(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct eq {
|
||||||
|
bool operator()(ddnf_node* n1, ddnf_node* n2) const {
|
||||||
|
return n1->get_tbv() == n2->get_tbv();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hash {
|
||||||
|
unsigned operator()(ddnf_node* n) const {
|
||||||
|
return n->get_tbv().get_hash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned get_id() const { return m_id; }
|
||||||
|
|
||||||
unsigned num_children() const { return m_children.size(); }
|
unsigned num_children() const { return m_children.size(); }
|
||||||
|
|
||||||
ddnf_node* operator[](unsigned index) { return m_children[index].get(); }
|
ddnf_node* operator[](unsigned index) { return m_children[index].get(); }
|
||||||
|
|
||||||
tbv const& get_tbv() const { return m_tbv; }
|
tbv const& get_tbv() const { return m_tbv; }
|
||||||
|
vector<dot> const& pos() const { return m_pos; }
|
||||||
|
vector<dot> const& neg() const { return m_neg; }
|
||||||
|
|
||||||
void add_child(ddnf_node* n);
|
void add_child(ddnf_node* n);
|
||||||
|
|
||||||
|
@ -215,17 +280,40 @@ namespace datalog {
|
||||||
|
|
||||||
bool contains_child(ddnf_node* n) const;
|
bool contains_child(ddnf_node* n) const;
|
||||||
|
|
||||||
|
ddnf_node* add_pos(dot const& d) { m_pos.push_back(d); return this; }
|
||||||
|
|
||||||
|
ddnf_node* add_neg(dot const& d) { m_neg.push_back(d); return this; }
|
||||||
|
|
||||||
|
void display(std::ostream& out) const {
|
||||||
|
out << "node[";
|
||||||
|
m_tbv.display(out);
|
||||||
|
for (unsigned i = 0; i < m_children.size(); ++i) {
|
||||||
|
out << " " << m_children[i]->get_id();
|
||||||
|
}
|
||||||
|
out << "]";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef ptr_hashtable<ddnf_node, ddnf_node::hash, ddnf_node::eq> ddnf_nodes;
|
||||||
|
|
||||||
class ddnf_mgr {
|
class ddnf_mgr {
|
||||||
unsigned m_num_bits;
|
unsigned m_num_bits;
|
||||||
ddnf_node* m_root;
|
ddnf_node* m_root;
|
||||||
ddnf_node_vector m_nodes;
|
ddnf_node_vector m_noderefs;
|
||||||
vector<dot> m_dots;
|
ddnf_nodes m_nodes;
|
||||||
|
ptr_vector<ddnf_nodes> m_tables;
|
||||||
|
map<dot, ddnf_nodes*, dot::hash, dot::eq> m_dots;
|
||||||
|
bool m_internalized;
|
||||||
public:
|
public:
|
||||||
ddnf_mgr(unsigned n): m_num_bits(n), m_nodes(*this) {
|
ddnf_mgr(unsigned n): m_num_bits(n), m_noderefs(*this), m_internalized(false) {
|
||||||
m_root = alloc(ddnf_node, *this, tbv(n, BIT_x));
|
m_root = alloc(ddnf_node, *this, tbv(n, BIT_x), m_nodes.size());
|
||||||
m_nodes.push_back(m_root);
|
m_noderefs.push_back(m_root);
|
||||||
|
m_nodes.insert(m_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
~ddnf_mgr() {
|
||||||
|
m_noderefs.reset();
|
||||||
|
std::for_each(m_tables.begin(), m_tables.end(), delete_proc<ddnf_nodes>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void inc_ref(ddnf_node* n) {
|
void inc_ref(ddnf_node* n) {
|
||||||
|
@ -233,38 +321,84 @@ namespace datalog {
|
||||||
}
|
}
|
||||||
|
|
||||||
void dec_ref(ddnf_node* n) {
|
void dec_ref(ddnf_node* n) {
|
||||||
unsigned count = n->dec_ref();
|
n->dec_ref();
|
||||||
NOT_IMPLEMENTED_YET();
|
}
|
||||||
|
|
||||||
|
void insert(dot const& d) {
|
||||||
|
SASSERT(!m_internalized);
|
||||||
|
if (m_dots.contains(d)) return;
|
||||||
|
ddnf_nodes* ns = alloc(ddnf_nodes);
|
||||||
|
m_tables.push_back(ns);
|
||||||
|
m_dots.insert(d, ns);
|
||||||
|
insert(d.pos())->add_pos(d);
|
||||||
|
for (unsigned i = 0; i < d.size(); ++i) {
|
||||||
|
insert(d.neg(i))->add_neg(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void display(std::ostream& out) const {
|
||||||
|
for (unsigned i = 0; i < m_noderefs.size(); ++i) {
|
||||||
|
m_noderefs[i]->display(out);
|
||||||
|
out << "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void insert_new(ddnf_node& root, ddnf_node* new_n,
|
|
||||||
ptr_vector<tbv>& new_intersections) {
|
ddnf_node* insert(tbv const& t) {
|
||||||
SASSERT(root.get_tbv().is_superset(new_n->get_tbv()));
|
vector<tbv> new_tbvs;
|
||||||
// if (root == *new_n) return;
|
new_tbvs.push_back(t);
|
||||||
|
for (unsigned i = 0; i < new_tbvs.size(); ++i) {
|
||||||
|
tbv const& nt = new_tbvs[i];
|
||||||
|
if (contains(nt)) continue;
|
||||||
|
ddnf_node* n = alloc(ddnf_node, *this, nt, m_noderefs.size());
|
||||||
|
m_noderefs.push_back(n);
|
||||||
|
m_nodes.insert(n);
|
||||||
|
insert(*m_root, n, new_tbvs);
|
||||||
|
}
|
||||||
|
return find(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
ddnf_node* find(tbv const& t) {
|
||||||
|
ddnf_node dummy(*this, t, 0);
|
||||||
|
return *(m_nodes.find(&dummy));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(tbv const& t) {
|
||||||
|
ddnf_node dummy(*this, t, 0);
|
||||||
|
return m_nodes.contains(&dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(ddnf_node& root, ddnf_node* new_n, vector<tbv>& new_intersections) {
|
||||||
|
tbv const& new_tbv = new_n->get_tbv();
|
||||||
|
|
||||||
|
SASSERT(new_tbv.is_subset(root.get_tbv()));
|
||||||
|
if (&root == new_n) return;
|
||||||
bool inserted = false;
|
bool inserted = false;
|
||||||
for (unsigned i = 0; i < root.num_children(); ++i) {
|
for (unsigned i = 0; i < root.num_children(); ++i) {
|
||||||
ddnf_node& child = *(root[i]);
|
ddnf_node& child = *(root[i]);
|
||||||
if (child.get_tbv().is_superset(new_n->get_tbv())) {
|
if (child.get_tbv().is_superset(new_tbv)) {
|
||||||
inserted = true;
|
inserted = true;
|
||||||
insert_new(child, new_n, new_intersections);
|
insert(child, new_n, new_intersections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (inserted) return;
|
if (inserted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ddnf_node_vector subset_children(*this);
|
ddnf_node_vector subset_children(*this);
|
||||||
|
tbv intr;
|
||||||
for (unsigned i = 0; i < root.num_children(); ++i) {
|
for (unsigned i = 0; i < root.num_children(); ++i) {
|
||||||
ddnf_node& child = *(root[i]);
|
ddnf_node& child = *(root[i]);
|
||||||
// cannot be superset
|
// cannot be superset
|
||||||
|
SASSERT(!child.get_tbv().is_superset(new_tbv));
|
||||||
// checking for subset
|
// checking for subset
|
||||||
if (child.get_tbv().is_subset(new_n->get_tbv())) {
|
if (child.get_tbv().is_subset(new_tbv)) {
|
||||||
subset_children.push_back(&child);
|
subset_children.push_back(&child);
|
||||||
}
|
}
|
||||||
// this means there is a non-full intersection
|
// this means there is a non-full intersection
|
||||||
else {
|
else {
|
||||||
tbv intr;
|
if (intersect(child.get_tbv(), new_tbv, intr)) {
|
||||||
// TBD intersect(child.get_tbv(), new_n->get_tbv(), intr);
|
new_intersections.push_back(intr);
|
||||||
if (!intr.empty()) {
|
|
||||||
// TBD new_intersections.push_back(intr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,126 +409,50 @@ namespace datalog {
|
||||||
root.add_child(new_n);
|
root.add_child(new_n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void internalize() {
|
||||||
#if 0
|
// create map: dot |-> ddnf_node set
|
||||||
|
if (!m_internalized) {
|
||||||
DDNFNode InsertTBV(TernaryBitVector aTbv)
|
vector<dot> dots1, dots2;
|
||||||
{
|
add_pos(*m_root, dots1);
|
||||||
foreach (DDNFNode node in allNodes)
|
del_neg(*m_root, dots2);
|
||||||
{
|
m_internalized = true;
|
||||||
if (node.tbv.IsEqualset(aTbv))
|
|
||||||
{
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DDNFNode newNode = new DDNFNode(aTbv);
|
|
||||||
this.allNodes.Add(newNode);
|
|
||||||
List<TernaryBitVector> newIntersections = new List<TernaryBitVector>();
|
|
||||||
InsertNewNode(this.root, newNode, newIntersections);
|
|
||||||
|
|
||||||
// add the TBVs corresponding to new intersections
|
|
||||||
foreach (TernaryBitVector newIntr in newIntersections)
|
|
||||||
{
|
|
||||||
// this assert guarantees termination since you can only
|
|
||||||
// insert smaller tbvs recursively
|
|
||||||
Debug.Assert(!newIntr.IsSupset(aTbv));
|
|
||||||
|
|
||||||
InsertTBV(newIntr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InsertDot(DOT aDot)
|
|
||||||
{
|
|
||||||
this.allDots.Add(aDot);
|
|
||||||
DDNFNode posNode = InsertTBV(aDot.pos);
|
|
||||||
List<DDNFNode> negNodes = new List<DDNFNode>();
|
|
||||||
foreach (TernaryBitVector neg in aDot.negs)
|
|
||||||
{
|
|
||||||
negNodes.Add(InsertTBV(neg));
|
|
||||||
}
|
|
||||||
|
|
||||||
posNode.posDots.Add(aDot);
|
|
||||||
foreach (DDNFNode negNode in negNodes)
|
|
||||||
{
|
|
||||||
negNode.negDots.Add(aDot);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all negNodes for each dot in dot2Nodes
|
ddnf_nodes const& lookup(dot const& d) const {
|
||||||
void RemoveNegNodesForAllDots(DDNFNode aNode, HashSet<DOT> activeDots,
|
return *m_dots.find(d);
|
||||||
ref Dictionary<DOT, HashSet<DDNFNode>> dot2Nodes)
|
|
||||||
{
|
|
||||||
foreach (DOT negDot in aNode.negDots)
|
|
||||||
{
|
|
||||||
activeDots.Add(negDot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (DOT activeDot in activeDots)
|
void add_pos(ddnf_node& n, vector<dot>& active) {
|
||||||
{
|
for (unsigned i = 0; i < n.pos().size(); ++i) {
|
||||||
dot2Nodes[activeDot].Remove(aNode);
|
active.push_back(n.pos()[i]);
|
||||||
|
// TBD: Garvit; prove (or disprove): there are no repetitions.
|
||||||
|
// the argument may be that the sub-expressions are
|
||||||
|
// traversed in DFS order, and that repeated dots
|
||||||
|
// cannot occur below each-other.
|
||||||
}
|
}
|
||||||
|
for (unsigned i = 0; i < active.size(); ++i) {
|
||||||
foreach (DDNFNode child in aNode.children)
|
m_dots.find(active[i])->insert(&n);
|
||||||
|
}
|
||||||
{
|
for (unsigned i = 0; i < n.num_children(); ++i) {
|
||||||
RemoveNegNodesForAllDots(child, new HashSet<DOT>(activeDots), ref dot2Nodes);
|
vector<dot> active1(active);
|
||||||
|
add_pos(*n[i], active1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add all posNodes for each dot in dot2Nodes
|
void del_neg(ddnf_node& n, vector<dot>& active) {
|
||||||
void AddPosNodesForAllDots(DDNFNode aNode, HashSet<DOT> activeDots,
|
for (unsigned i = 0; i < n.neg().size(); ++i) {
|
||||||
ref Dictionary<DOT, HashSet<DDNFNode>> dot2Nodes)
|
active.push_back(n.neg()[i]);
|
||||||
{
|
|
||||||
foreach (DOT posDot in aNode.posDots)
|
|
||||||
{
|
|
||||||
activeDots.Add(posDot);
|
|
||||||
}
|
}
|
||||||
|
for (unsigned i = 0; i < active.size(); ++i) {
|
||||||
foreach (DOT activeDot in activeDots)
|
m_dots.find(active[i])->remove(&n);
|
||||||
{
|
|
||||||
dot2Nodes[activeDot].Add(aNode);
|
|
||||||
}
|
}
|
||||||
|
for (unsigned i = 0; i < n.num_children(); ++i) {
|
||||||
foreach (DDNFNode child in aNode.children)
|
vector<dot> active1(active);
|
||||||
{
|
del_neg(*n[i], active1);
|
||||||
AddPosNodesForAllDots(child, new HashSet<DOT>(activeDots), ref dot2Nodes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<DOT, HashSet<DDNFNode>> NodesForAllDots()
|
|
||||||
{
|
|
||||||
Dictionary<DOT, HashSet<DDNFNode>> dot2Nodes =
|
|
||||||
new Dictionary<DOT, HashSet<DDNFNode>>();
|
|
||||||
|
|
||||||
foreach (DOT dot in allDots)
|
|
||||||
{
|
|
||||||
dot2Nodes[dot] = new HashSet<DDNFNode>();
|
|
||||||
}
|
|
||||||
AddPosNodesForAllDots(this.root, new HashSet<DOT>(), ref dot2Nodes);
|
|
||||||
RemoveNegNodesForAllDots(this.root, new HashSet<DOT>(), ref dot2Nodes);
|
|
||||||
|
|
||||||
return dot2Nodes;
|
|
||||||
|
|
||||||
}
|
|
||||||
public string PrintNodes()
|
|
||||||
{
|
|
||||||
StringBuilder retVal = new StringBuilder();
|
|
||||||
retVal.Append("<DDNF: ");
|
|
||||||
foreach (DDNFNode node in allNodes)
|
|
||||||
{
|
|
||||||
retVal.Append("\n").Append(node.ToString());
|
|
||||||
}
|
|
||||||
return retVal.Append(">").ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void ddnf_node::add_child(ddnf_node* n) {
|
void ddnf_node::add_child(ddnf_node* n) {
|
||||||
|
|
|
@ -224,6 +224,10 @@ bool bit_vector::contains(bit_vector const& other) const {
|
||||||
return (m_data[n-1] & other_data) == other_data;
|
return (m_data[n-1] & other_data) == other_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned bit_vector::get_hash() const {
|
||||||
|
return string_hash(reinterpret_cast<char const* const>(m_data), size()/8, 0);
|
||||||
|
}
|
||||||
|
|
||||||
void fr_bit_vector::reset() {
|
void fr_bit_vector::reset() {
|
||||||
unsigned sz = size();
|
unsigned sz = size();
|
||||||
unsigned_vector::const_iterator it = m_one_idxs.begin();
|
unsigned_vector::const_iterator it = m_one_idxs.begin();
|
||||||
|
|
|
@ -126,6 +126,8 @@ public:
|
||||||
return m_data[word_idx];
|
return m_data[word_idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned get_hash() const;
|
||||||
|
|
||||||
bool get(unsigned bit_idx) const {
|
bool get(unsigned bit_idx) const {
|
||||||
SASSERT(bit_idx < size());
|
SASSERT(bit_idx < size());
|
||||||
bool r = (get_bit_word(bit_idx) & get_pos_mask(bit_idx)) != 0;
|
bool r = (get_bit_word(bit_idx) & get_pos_mask(bit_idx)) != 0;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue