Commit 9d8899ca authored by Mark Olesen's avatar Mark Olesen

ENH: additional HashTable emplace/insert/set methods (#1286)

- support move insert/set and emplace insertion.

  These adjustments can be used for improved memory efficiency, and
  allow hash tables of non-copyable objects (eg, std::unique_ptr).

- extend special HashTable output treatment to include pointer-like
  objects such as autoPtr and unique_ptr.

ENH: HashTable::at() method with checking. Fatal if entry does not exist.
parent 2fd2c358
......@@ -2,7 +2,7 @@
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2017-2018 OpenCFD Ltd.
\\ / A nd | Copyright (C) 2017-2019 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
| Copyright (C) 2011 OpenFOAM Foundation
......@@ -25,9 +25,9 @@ License
Description
\*---------------------------------------------------------------------------*/
#include <memory>
#include <iostream>
#include "autoPtr.H"
#include "HashPtrTable.H"
......@@ -56,14 +56,14 @@ void printTable(const HashPtrTable<T>& table)
Info<< ")" << endl;
// Values only, with for-range
// Iterate across values, with for-range
Info<< "values (";
for (auto val : table)
for (const auto& ptr : table)
{
Info<< ' ';
if (val)
if (ptr)
{
Info<< *val;
Info<< *ptr;
}
else
{
......@@ -86,8 +86,27 @@ int main()
myTable.set("natlog", new double(2.718282));
myTable.insert("sqrt2", autoPtr<double>::New(1.414214));
HashTable<std::unique_ptr<double>, word, string::hash> myTable1;
myTable1.set("abc", std::unique_ptr<double>(new double(42.1)));
myTable1.set("pi", std::unique_ptr<double>(new double(3.14159)));
myTable1.set("natlog", std::unique_ptr<double>(new double(2.718282)));
HashTable<autoPtr<double>, word, string::hash> myTable2;
myTable2.set("abc", autoPtr<double>(new double(42.1)));
myTable2.set("pi", autoPtr<double>(new double(3.14159)));
myTable2.set("natlog", autoPtr<double>(new double(2.718282)));
myTable2.insert("sqrt2", autoPtr<double>::New(1.414214));
// Info<< myTable << endl;
printTable(myTable);
Info<< myTable2 << nl;
auto iter2 = myTable2.find("pi");
Info<<"PI: " << **iter2 << nl;
HashPtrTable<double> copy(myTable);
......
......@@ -249,6 +249,28 @@ int main(int argc, char *argv[])
Info<< nl << "Ending scope" << nl;
}
{
Info<< nl << "Table<labelList> copy/move/emplace insertion" << nl;
HashTable<labelList> ltable1(0);
ltable1.insert("abc", identity(2));
ltable1.insert("def", identity(3));
ltable1.insert("ghi", identity(4));
ltable1.emplace("jkl", 10, -35);
ltable1.emplace("mno");
labelList list1(identity(4, -4));
Info<<"move insert " << list1 << nl;
ltable1.insert("pqr", std::move(list1));
Info<<"after insert " << list1 << nl;
Info<< nl << "HashTable<labelList>: "
<< ltable1 << nl;
}
Info<< "\nEnd\n" << endl;
return 0;
......
......@@ -145,15 +145,15 @@ int main(int argc, char *argv[])
const label nElem = 1000000;
argList::noBanner();
argList::addBoolOption("std", "use std::unordered_map or std::set");
argList::addBoolOption("set", "test HashSet");
argList::addBoolOption("find", "test find");
argList::addBoolOption("set", "test HashSet");
argList::addBoolOption("std", "std::unordered_map or std::unordered_set");
argList args(argc, argv);
const bool optStd = args.found("std");
const bool optSet = args.found("set");
const bool optFnd = args.found("find");
const bool optSet = args.found("set");
const bool optStd = args.found("std");
cpuTime timer;
......@@ -171,7 +171,7 @@ int main(int argc, char *argv[])
if (false)
{
// verify that resizing around (0) doesn't fail
// Verify that resizing around (0) doesn't fail
HashTable<label, label, Hash<label>> map(32);
printInfo(Info, map) << endl;
......@@ -243,7 +243,7 @@ int main(int argc, char *argv[])
#ifdef ORDERED
Info<< "using stl::map" << endl;
#else
Info<< "using stl::unordered_set" << endl;
Info<< "using stl::unordered_map" << endl;
#endif
for (label loopi = 0; loopi < nLoops; ++loopi)
......
......@@ -48,17 +48,21 @@ using namespace Foam;
template<class T, class Key, class Hash> class HashSorter;
template<class T, class Key, class Hash>
Ostream& operator<<(Ostream& os, const HashSorter<T, Key, Hash>& sorter);
Ostream& operator<<
(
Ostream& os,
const HashSorter<T, Key, Hash>& sorter
);
template<class T, class Key, class Hash>
class HashSorter
{
const HashTable<T,Key,Hash>& table;
const HashTable<T, Key, Hash>& table;
public:
HashSorter(const HashTable<T,Key,Hash>& ht)
HashSorter(const HashTable<T, Key, Hash>& ht)
:
table(ht)
{}
......
......@@ -47,7 +47,7 @@ SourceFiles
namespace Foam
{
// Forward declarations
// Forward Declarations
class bitSet;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
......@@ -143,7 +143,11 @@ struct plusEqOp
//- List of values from HashTable, optionally sorted.
template<class T, class Key, class Hash>
List<T> values(const HashTable<T, Key, Hash>& tbl, const bool doSort=false)
List<T> values
(
const HashTable<T, Key, Hash>& tbl,
const bool doSort=false
)
{
List<T> output(tbl.size());
......
......@@ -422,28 +422,28 @@ public:
//- Combine entries from HashSets
template<class Key, class Hash>
HashSet<Key,Hash> operator|
HashSet<Key, Hash> operator|
(
const HashSet<Key,Hash>& hash1,
const HashSet<Key,Hash>& hash2
const HashSet<Key, Hash>& hash1,
const HashSet<Key, Hash>& hash2
);
//- Create a HashSet that only contains entries found in both HashSets
template<class Key, class Hash>
HashSet<Key,Hash> operator&
HashSet<Key, Hash> operator&
(
const HashSet<Key,Hash>& hash1,
const HashSet<Key,Hash>& hash2
const HashSet<Key, Hash>& hash1,
const HashSet<Key, Hash>& hash2
);
//- Create a HashSet that only contains unique entries (xor)
template<class Key, class Hash>
HashSet<Key,Hash> operator^
HashSet<Key, Hash> operator^
(
const HashSet<Key,Hash>& hash1,
const HashSet<Key,Hash>& hash2
const HashSet<Key, Hash>& hash1,
const HashSet<Key, Hash>& hash2
);
......
......@@ -300,11 +300,12 @@ Foam::label Foam::HashTable<T, Key, Hash>::countEntries
template<class T, class Key, class Hash>
template<class... Args>
bool Foam::HashTable<T, Key, Hash>::setEntry
(
const bool overwrite,
const Key& key,
const T& obj,
const bool overwrite
Args&&... args
)
{
if (!capacity_)
......@@ -330,9 +331,10 @@ bool Foam::HashTable<T, Key, Hash>::setEntry
if (!curr)
{
// Not found, insert it at the head
table_[index] = new node_type(key, obj, table_[index]);
++size_;
table_[index] =
new node_type(table_[index], key, std::forward<Args>(args)...);
++size_;
if (double(size_)/capacity_ > 0.8 && capacity_ < maxTableSize)
{
#ifdef FULLDEBUG
......@@ -360,7 +362,7 @@ bool Foam::HashTable<T, Key, Hash>::setEntry
// or that it behaves the same as a copy construct.
delete curr;
ep = new node_type(key, obj, ep);
ep = new node_type(ep, key, std::forward<Args>(args)...);
// Replace current element - within list or insert at the head
if (prev)
......@@ -838,7 +840,7 @@ bool Foam::HashTable<T, Key, Hash>::operator==
{
const const_iterator other(this->cfind(iter.key()));
if (!other.found() || other.val() != iter.val())
if (!other.good() || other.val() != iter.val())
{
return false;
}
......
......@@ -97,7 +97,7 @@ SourceFiles
namespace Foam
{
// Forward declarations
// Forward Declarations
template<class T> class List;
template<class T> class UList;
......@@ -155,7 +155,8 @@ class HashTable
//- Assign a new hash-entry to a possibly already existing key.
// \return True if the new entry was set.
bool setEntry(const Key& key, const T& obj, const bool overwrite);
template<class... Args>
bool setEntry(const bool overwrite, const Key& key, Args&&... args);
public:
......@@ -257,6 +258,12 @@ public:
//- Return true if the hash table is empty
inline bool empty() const;
//- Find and return a hashed entry. FatalError if it does not exist.
inline T& at(const Key& key);
//- Find and return a hashed entry. FatalError if it does not exist.
inline const T& at(const Key& key) const;
//- Return true if hashed entry is found in table
inline bool found(const Key& key) const;
......@@ -361,16 +368,27 @@ public:
// Edit
//- Insert a new entry, not overwriting existing entries.
// \return True if the entry inserted, which means that it did
// not previously exist in the table.
//- Emplace insert a new entry, not overwriting existing entries.
// \return True if the entry did not previously exist in the table.
template<class... Args>
inline bool emplace(const Key& key, Args&&... args);
//- Copy insert a new entry, not overwriting existing entries.
// \return True if the entry did not previously exist in the table.
inline bool insert(const Key& key, const T& obj);
//- Assign a new entry, overwriting existing entries.
//
//- Move insert a new entry, not overwriting existing entries.
// \return True if the entry did not previously exist in the table.
inline bool insert(const Key& key, T&& obj);
//- Copy assign a new entry, overwriting existing entries.
// \return True, since it always overwrites any entries.
inline bool set(const Key& key, const T& obj);
//- Move assign a new entry, overwriting existing entries.
// \return True, since it always overwrites any entries.
inline bool set(const Key& key, T&& obj);
//- Erase an entry specified by given iterator
// This invalidates the iterator until the next ++ operation.
//
......@@ -513,20 +531,20 @@ public:
inline T& operator()(const Key& key, const T& deflt);
//- Copy assign
void operator=(const HashTable<T, Key, Hash>& rhs);
void operator=(const this_type& rhs);
//- Copy assign from an initializer list
void operator=(std::initializer_list<std::pair<Key, T>> rhs);
//- Move assign
void operator=(HashTable<T, Key, Hash>&& rhs);
void operator=(this_type&& rhs);
//- Equality. Tables are equal if all keys and values are equal,
//- independent of order or underlying storage size.
bool operator==(const HashTable<T, Key, Hash>& rhs) const;
bool operator==(const this_type& rhs) const;
//- The opposite of the equality operation.
bool operator!=(const HashTable<T, Key, Hash>& rhs) const;
bool operator!=(const this_type& rhs) const;
//- Add entries into this HashTable
this_type& operator+=(const this_type& rhs);
......@@ -584,8 +602,7 @@ protected:
// This can be used directly instead of comparing to end()
inline bool good() const;
//- True if iterator points to an entry
// This can be used directly instead of comparing to end()
//- True if iterator points to an entry - same as good()
inline bool found() const;
//- The key associated with the iterator
......@@ -703,7 +720,7 @@ public:
{}
// Member functions/operators
// Member Functions/Operators
//- Non-const access to referenced object (value)
using Iterator<false>::val;
......@@ -719,7 +736,7 @@ public:
template<class TypeT = T>
typename std::enable_if
<
std::is_pointer<TypeT>::value,
Detail::isPointer<TypeT>::value,
T
>::type operator->() const { return this->val(); }
......@@ -773,7 +790,7 @@ public:
{}
// Member functions/operators
// Member Functions/Operators
//- Const access to referenced value
using Iterator<true>::val;
......@@ -789,7 +806,7 @@ public:
template<class TypeT = T>
typename std::enable_if
<
std::is_pointer<TypeT>::value,
Detail::isPointer<TypeT>::value,
const T
>::type operator->() const { return this->val(); }
......
......@@ -36,6 +36,7 @@ SourceFiles
#define HashTableDetail_H
#include "zero.H"
#include <memory>
#include <utility>
#include <type_traits>
......@@ -44,14 +45,32 @@ SourceFiles
namespace Foam
{
// Forward declarations
// Forward Declarations
class Ostream;
template<class T> class autoPtr;
namespace Detail
{
/*---------------------------------------------------------------------------*\
Class Detail::HashTablePair Declaration
Class isPointer Declaration
\*---------------------------------------------------------------------------*/
//- Test for pointer-like behaviour
template<class T>
struct isPointer : public std::is_pointer<T> {};
//- An autoPtr is pointer-like
template<class T>
struct isPointer<autoPtr<T>> : public std::true_type {};
//- A unique_ptr is pointer-like
template<class T>
struct isPointer<std::unique_ptr<T>> : public std::true_type {};
/*---------------------------------------------------------------------------*\
Class HashTablePair Declaration
\*---------------------------------------------------------------------------*/
//- Internal storage type for HashTable entries
......@@ -97,16 +116,17 @@ struct HashTablePair
void operator=(const HashTablePair&) = delete;
//- Construct from key, value, next pointer
//- Construct from next pointer, key, contents
template<class... Args>
HashTablePair
(
HashTablePair* next,
const key_type& key,
const mapped_type& val,
HashTablePair* next
Args&&... args
)
:
key_(key),
val_(val),
val_(std::forward<Args>(args)...),
next_(next)
{}
......@@ -131,7 +151,7 @@ struct HashTablePair
//- Write (key, val) pair - for pointer types
template<class TypeT = T>
typename std::enable_if<std::is_pointer<TypeT>::value, void>::type
typename std::enable_if<Detail::isPointer<TypeT>::value, void>::type
print(Ostream& os) const
{
os << key_;
......@@ -144,7 +164,7 @@ struct HashTablePair
//- Write (key, val) pair - for non-pointer types
template<class TypeT = T>
typename std::enable_if<!std::is_pointer<TypeT>::value, void>::type
typename std::enable_if<!Detail::isPointer<TypeT>::value, void>::type
print(Ostream& os) const
{
os << key_ << ' ' << val_;
......@@ -153,7 +173,7 @@ struct HashTablePair
/*---------------------------------------------------------------------------*\
Class Detail::HashTableSingle Declaration
Class HashTableSingle Declaration
\*---------------------------------------------------------------------------*/
//- Internal storage type for HashSet entries
......@@ -196,12 +216,13 @@ struct HashTableSingle
void operator=(const HashTableSingle&) = delete;
//- Construct from key, (ununsed) value, next pointer
//- Construct from next pointer, key, (ununsed) contents
template<class... Args>
HashTableSingle
(
HashTableSingle* next,
const key_type& key,
const mapped_type&,
HashTableSingle* next
Args&&...
)
:
key_(key),
......
......@@ -61,12 +61,46 @@ inline bool Foam::HashTable<T, Key, Hash>::empty() const
}
template<class T, class Key, class Hash>
inline T& Foam::HashTable<T, Key, Hash>::at(const Key& key)
{
const iterator iter(this->find(key));
if (!iter.good())
{
FatalErrorInFunction
<< key << " not found in table. Valid entries: "
<< toc()
<< exit(FatalError);
}
return iter.val();
}
template<class T, class Key, class Hash>
inline const T& Foam::HashTable<T, Key, Hash>::at(const Key& key) const
{
const const_iterator iter(this->cfind(key));
if (!iter.good())
{
FatalErrorInFunction
<< key << " not found in table. Valid entries: "
<< toc()
<< exit(FatalError);
}
return iter.val();
}
template<class T, class Key, class Hash>
inline bool Foam::HashTable<T, Key, Hash>::found(const Key& key) const
{
if (size_)
{
return Iterator<true>(this, key).found();
return Iterator<true>(this, key).good();
}
return false;
......@@ -116,6 +150,18 @@ Foam::HashTable<T, Key, Hash>::cfind
}
template<class T, class Key, class Hash>
template<class... Args>
inline bool Foam::HashTable<T, Key, Hash>::emplace
(
const Key& key,
Args&&... args
)
{
return this->setEntry(false, key, std::forward<Args>(args)...);
}
template<class T, class Key, class Hash>
inline bool Foam::HashTable<T, Key, Hash>::insert
(
......@@ -123,7 +169,18 @@ inline bool Foam::HashTable<T, Key, Hash>::insert
const T& val
)
{
return this->setEntry(key, val, false); // No overwrite
return this->setEntry(false, key, val);
}
template<class T, class Key, class Hash>
inline bool Foam::HashTable<T, Key, Hash>::insert
(
const Key& key,
T&& val
)
{
return this->setEntry(false, key, std::forward<T>(val));
}
......@@ -134,7 +191,18 @@ inline bool Foam::HashTable<T, Key, Hash>::set
const T& val
)
{
return this->setEntry(key, val, true); // Overwrite
return this->setEntry(true, key, val); // Overwrite
}
template<class T, class Key, class Hash>
inline bool Foam::HashTable<T, Key, Hash>::set
(
const Key& key,
T&& val
)
{
return this->setEntry(true, key, std::forward<T>(val)); // Overwrite
}
......@@ -146,7 +214,7 @@ inline const T& Foam::HashTable<T, Key, Hash>::lookup
) const
{
const const_iterator iter(this->cfind(key));
return iter.found() ? iter.val() : deflt;
return iter.good() ? iter.val() : deflt;
}
......@@ -157,7 +225,7 @@ inline T& Foam::HashTable<T, Key, Hash>::operator[](const Key& key)
{
const iterator iter(this->find(key));
if (!iter.found())
if (!iter.good())
{
FatalErrorInFunction
<< key << " not found in table. Valid entries: "
......@@ -174,7 +242,7 @@ inline const T& Foam::HashTable<T, Key, Hash>::operator[](const Key& key) const
{
const const_iterator iter(this->cfind(key));