This piece below is supposed to be primarily for a string view with T={char, const char} being the primary intended template instantiation target.
The cmp function is supposed to compare the views analogously to strcmp.
The problem is that while char* happily converts to const char* I don't know how to get SVec<char> to convert to SVec<const char> just as happily.
The last line (cout<<(cmp(rv, rvc));) won't compile. I have to do the convertion explicitly (cmp(SVec<const char>(rv), rvc)). Can it be automatic like with char* to const char*?
The code (much simplified):
template <typename T>
class SVec {
protected:
T* begin_;
size_t size_;
public:
SVec(T* begin, size_t size) : begin_(begin), size_(size) {};
SVec(T* begin, T* end) : begin_(begin), size_(end-begin) {};
SVec(T* begin) : begin_(begin) { while (*(begin++)) {}; size_ = begin - 1 - begin_; }
//^null element indicates the end
///Conversion
operator SVec<const T>() const { return SVec<const T>(begin_, size_); }
};
//General lexicographic compare
template <typename T>
inline int cmp(const SVec<const T>& l, const SVec<const T> & r){
return 1;
}
//Char specialization
template <> inline int cmp<char>(const SVec<const char>& l, const SVec<const char>& r){
return 1;
}
//Explicit instantiation
template int cmp<char>(const SVec<const char>& l, const SVec<const char>& r);
#include <iostream>
int main(){
using namespace std;
char ar[] = "st";
SVec<char> sv = ar;
SVec<const char> svc = "str";
cout<<(cmp(SVec<const char>(sv), svc));
cout<<(cmp(sv, svc));
}
So the first thing you should probably do is make cmp a Koenig operator.
Then we can tag dispatch between the char and non-char versions:
template <typename T>
class SVec {
private:
static T* find_end(T* in) {
// I think while(*in)++in; would be better
// then the end is the null, not one-past-the-null.
while(*in++) {};
return in;
}
protected:
T* begin_ = nullptr;
size_t size_ = 0;
public:
SVec() = default;
SVec(SVec const&) = default;
SVec(T* begin, size_t size) : begin_(begin), size_(size) {};
SVec(T* begin, T* end) : SVec(begin, end-begin) {}
SVec(T* begin) : SVec(begin, find_end(begin)) {}
operator SVec<const T>() const { return SVec<const T>(begin_, size_); }
friend int cmp(SVec<T> l, SVec<T> r) {
return cmp_impl(l, r, std::is_same<std::decay_t<T>,char>{});
}
private:
static int cmp_impl(SVec<const char> l, SVec<const char> r, std::true_type){
return 1;
}
static int cmp_impl(SVec<const T> l, SVec<const T> r, std::false_type){
return 1;
}
};
std::decay_t and enable_if_t are C++14, but are just short versions of the typename spam _t-less versions.
Notice I take things by value instead of const& : a pointer and a size_t do not merit passing by reference.
I also forward all ctors into 2 bottlenecks.
...
The Koenig operator friend int cmp uses ADL to be found. It is not a template function, but rather a function that is generated for each template class instance, which is an important distinction.
Koenig operators avoid the problems of template operators, while allowing them to vary with the type of the template. Such an operator can only be found via ADL (argument dependent lookup).
It then dispatches to the _impl overloads (which are now const-correct) based on if T is a char or not at compile time.
Related
It's been a few times we've found nondeterministic issues in the codebase I'm working on, and so far it's almost been root caused to the use of std::[unordered_]map/set<T*,U>, where the key is a pointer, combined with iteration on the map, usually in the form of a range-based for loop (since pointer values may change between executions, iteration order is nondeterministic).
I was wondering if there was some black template magic one could use to inject a static_assert when begin() is called on such a container. I think begin() is the best place to do this, or maybe iterator::operator++, since constructing iterators otherwise, such as a result of find(), is okay.
I thought I could overload std::begin, but the rules for range-based for loops state that .begin() is used if it exists. So, I'm out of ideas. Is there a clever trick to do this?
Further clarification: No custom comparator is involved, the direct value of the pointer (aka the address of the target object) is the key. This is fine for insertion and lookup, and only becomes a problem when iterating over the container since the order is based on unpredictable pointer values. I'm trying to find existing cases like this in a large existing codebase.
You can almost achieve the desired behavior with partial specializations:
20.5.4.2.1 The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.
Therefore, a simple specialization for std::map can be used to detect attempts to instantiate the template with a pointer key type:
#include <map>
namespace internal
{
// User-defined type trait
template<class Key, class T>
class DefaultAllocator
{
public:
using type = std::allocator<std::pair<const Key, T>>;
};
// Effectively the same as std::allocator, but a different type
template<class T>
class Allocator2 : public std::allocator<T> {};
}
namespace std
{
// Specialization for std::map with a pointer key type and the default allocator.
// The class inherits most of the implementation from
// std::map<Key*, T, Compare, ::internal::Allocator2<std::pair<Key*, T>>>
// to mimic the standard implementation.
template<class Key, class T, class Compare>
class map<Key*, T, Compare, typename ::internal::DefaultAllocator<Key*, T>::type> :
public map<Key*, T, Compare, ::internal::Allocator2<std::pair<Key*, T>>>
{
using base = map<Key*, T, Compare, ::internal::Allocator2<std::pair<Key*, T>>>;
using base::iterator;
using base::const_iterator;
public:
// Overload begin() and cbegin()
iterator begin() noexcept
{
static_assert(false, "OH NOES, A POINTER");
}
const_iterator begin() const noexcept
{
static_assert(false, "OH NOES, A POINTER");
}
const_iterator cbegin() const noexcept
{
static_assert(false, "OH NOES, A POINTER");
}
};
}
int main()
{
std::map<int, int> m1;
std::map<int*, int> m2;
// OK, not a specialization
m1[0] = 42;
for (auto& keyval : m1)
{
(void)keyval;
}
m2[nullptr] = 42; // Insertion is OK
for (auto& keyval : m2) // static_assert failure
{
(void)keyval;
}
}
However,
I haven't figured out a way to extend this for custom allocators: the declaration of the specialization has to depend on some user-defined type.
This is a terrible kludge, so I would only use it to find existing cases (rather than keeping as a static checker).
One approach to achieve a compile time failure for designated pointer types is to delete std::less, std::greater, std::hash, etc specializations for the specific pointer types that are susceptible to non-deterministic behavior (i.e. returned by interfaces). There are many options to provide "safe" functionality for pointer collections.
The following is a comprehensive example:
#include <cassert>
#include <memory>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#define DISABLE_NUMERIC_POINTER_SPECIALIZATIONS(T) \
namespace std { \
template <> struct hash<const T*> { std::size_t operator()(const T* obj) const = delete; }; \
template <> struct hash<T*> { std::size_t operator()(T* obj) const = delete; }; \
template <> struct less<const T*> { bool operator()(const T* lhs, const T* rhs) const = delete; }; \
template <> struct less<T*> { bool operator()(T* lhs, T* rhs) const = delete; }; \
template <> struct greater<const T*> { bool operator()(const T* lhs, const T* rhs) const = delete; }; \
template <> struct greater<T*> { bool operator()(T* lhs, T* rhs) const = delete; }; \
template <> struct less_equal<const T*> { bool operator()(const T* lhs, const T* rhs) const = delete; }; \
template <> struct less_equal<T*> { bool operator()(T* lhs, T* rhs) const = delete; }; \
template <> struct greater_equal<const T*> { bool operator()(const T* lhs, const T* rhs) const = delete; }; \
template <> struct greater_equal<T*> { bool operator()(T* lhs, T* rhs) const = delete; }; \
}
namespace NS {
class C {
public:
explicit C(int id) : m_id{id} {}
int id() const { return m_id; }
private:
int m_id;
};
inline bool operator ==(const C& lhs, const C& rhs) { return lhs.id() == rhs.id(); }
inline bool operator <(const C& lhs, const C& rhs) { return lhs.id() < rhs.id(); }
} // namespace NS
namespace std {
template <> struct hash<NS::C> { std::size_t operator()(const NS::C& obj) const { return obj.id(); } };
}
DISABLE_NUMERIC_POINTER_SPECIALIZATIONS(NS::C)
struct IndirectEqual {
template <typename T>
bool operator()(const T* lhs, const T* rhs) const {
return (lhs && rhs) ? *lhs == *rhs : lhs == rhs;
}
};
struct IndirectLess {
template <typename T>
bool operator()(const T* lhs, const T* rhs) const {
return (lhs && rhs) ? *lhs < *rhs : lhs < rhs;
}
};
struct IndirectGreater {
template <typename T>
bool operator()(const T* lhs, const T* rhs) const {
return (lhs && rhs) ? *lhs > *rhs : lhs > rhs;
}
};
struct IndirectHash {
template <typename T>
std::size_t operator()(const T* ptr) const {
return ptr ? std::hash<T>{}(*ptr) : std::numeric_limits<std::size_t>::max();
}
};
struct BuiltinLess {
template <typename T>
bool operator()(const T& lhs, const T& rhs) const { return lhs < rhs; }
};
struct SPLess {
template <typename T>
bool operator()(const std::shared_ptr<T>& lhs, const std::shared_ptr<T>& rhs) const { return lhs.get() < rhs.get(); }
};
struct BuiltinGreater {
template <typename T>
bool operator()(const T& lhs, const T& rhs) const { return lhs < rhs; };
};
struct PtrHash {
template <typename T>
std::size_t operator()(const T* ptr) const { return static_cast<std::size_t>(ptr); };
};
template <typename T>
class BasicSet : private std::set<T, BuiltinLess> {
public:
using std::set<T, BuiltinLess>::set;
using std::set<T, BuiltinLess>::find;
using std::set<T, BuiltinLess>::insert;
using std::set<T, BuiltinLess>::emplace;
using std::set<T, BuiltinLess>::end;
};
template <typename T>
class BasicSet<std::shared_ptr<T>> : private std::set<std::shared_ptr<T>, SPLess> {
public:
using std::set<std::shared_ptr<T>, SPLess>::set;
using std::set<std::shared_ptr<T>, SPLess>::find;
using std::set<std::shared_ptr<T>, SPLess>::insert;
using std::set<std::shared_ptr<T>, SPLess>::emplace;
using std::set<std::shared_ptr<T>, SPLess>::end;
};
int main()
{
// All of these decls result in a compiler error
// std::set<NS::C*> unsafe_s{new NS::C{1}, new NS::C{2}};
// std::map<NS::C*, int> unsafe_m{ {new NS::C{1}, 100} };
// std::unordered_set<NS::C*> unsafe_us{new NS::C{1}, new NS::C{2}};
// std::unordered_map<NS::C*, int> unsafe_um{ {new NS::C{1}, 123} };
std::set<NS::C*, IndirectLess> s{ new NS::C{1} };
std::unordered_set<NS::C*, IndirectHash> us1{ new NS::C{1} };
std::unordered_set<NS::C*, IndirectHash, IndirectEqual> us2{ new NS::C{1} };
auto c = new NS::C{1};
assert (s.find(c) != s.end());
assert (us1.find(c) == us1.end()); // pointers aren't equal
assert (us2.find(c) != us2.end()); // objects are equal
BasicSet<NS::C*> bs{ new NS::C{1} };
assert (bs.find(c) == bs.end()); // pointers aren't equal
auto sp1 = std::make_shared<NS::C>(10);
auto sp2 = std::make_shared<NS::C>(20);
BasicSet<std::shared_ptr<NS::C>> spset{sp1, sp2};
assert(spset.find(sp1) != spset.end());
return 0;
}
Note: This isn't perfect. E.G., one would need to disable 'volatile T*' and 'const volatile T*' variations. I'm sure there are other issues.
The following code is based on that found in Modern C++ programming cookbook, and is compiled in VS 2017:
#include <iostream>
using namespace std;
template <typename T, size_t const Size>
class dummy_array
{
T data[Size] = {};
public:
T const & GetAt(size_t const index) const
{
if (index < Size) return data[index];
throw std::out_of_range("index out of range");
}
// I have added this
T & GetAt(size_t const index)
{
if (index < Size) return data[index];
throw std::out_of_range("index out of range");
}
void SetAt(size_t const index, T const & value)
{
if (index < Size) data[index] = value;
else throw std::out_of_range("index out of range");
}
size_t GetSize() const { return Size; }
};
template <typename T, typename C, size_t const Size>
class dummy_array_iterator_type
{
public:
dummy_array_iterator_type(C& collection,
size_t const index) :
index(index), collection(collection)
{ }
bool operator!= (dummy_array_iterator_type const & other) const
{
return index != other.index;
}
T const & operator* () const
{
return collection.GetAt(index);
}
// I have added this
T & operator* ()
{
return collection.GetAt(index);
}
dummy_array_iterator_type const & operator++ ()
{
++index;
return *this;
}
private:
size_t index;
C& collection;
};
template <typename T, size_t const Size>
using dummy_array_iterator = dummy_array_iterator_type<T, dummy_array<T, Size>, Size>;
// I have added the const in 'const dummy_array_iterator_type'
template <typename T, size_t const Size>
using dummy_array_const_iterator = const dummy_array_iterator_type<T, dummy_array<T, Size> const, Size>;
template <typename T, size_t const Size>
inline dummy_array_iterator<T, Size> begin(dummy_array<T, Size>& collection)
{
return dummy_array_iterator<T, Size>(collection, 0);
}
template <typename T, size_t const Size>
inline dummy_array_iterator<T, Size> end(dummy_array<T, Size>& collection)
{
return dummy_array_iterator<T, Size>(collection, collection.GetSize());
}
template <typename T, size_t const Size>
inline dummy_array_const_iterator<T, Size> begin(dummy_array<T, Size> const & collection)
{
return dummy_array_const_iterator<T, Size>(collection, 0);
}
template <typename T, size_t const Size>
inline dummy_array_const_iterator<T, Size> end(dummy_array<T, Size> const & collection)
{
return dummy_array_const_iterator<T, Size>(collection, collection.GetSize());
}
int main(int nArgc, char** argv)
{
dummy_array<int, 10> arr;
for (auto&& e : arr)
{
std::cout << e << std::endl;
e = 100; // PROBLEM
}
const dummy_array<int, 10> arr2;
for (auto&& e : arr2) // ERROR HERE
{
std::cout << e << std::endl;
}
}
Now, the error is pointing at the line
T & operator* ()
stating
'return': cannot convert from 'const T' to 'T &'"
...which is raised from my range based for loop on arr2.
Why is the compiler choosing the none-constant version of operator*()?. I have looked at this for a long time; I think its because it thinks that the object on which it is calling this operator is not constant: this should be a dummy_array_const_iterator. But, this object has been declared to be constant via
template <typename T, size_t const Size>
using dummy_array_const_iterator = const dummy_array_iterator_type<T, dummy_array<T, Size> const, Size>;
...so I really don't understand what is happening. Can someone please clarify?
TIA
dummy_array_const_iterator::operator * should always return T const & regardless of constness of the iterator object itself.
The easiest way to achieve this is probably just to declare it with T const as underlying iterator value type:
template <typename T, size_t const Size>
using dummy_array_const_iterator = dummy_array_iterator_type<T const, dummy_array<T, Size> const, Size>;
Since you are returning the iterator by value its constness can be easily lost by c++ type deduction rules and just declaring dummy_array_const_iterator as alias to const dummy_array_iterator_type is not enough. i.e. the following fails:
#include <type_traits>
struct I { };
using C = I const;
C begin();
int bar()
{
auto x = begin(); // type of x is deduced as I
static_assert(std::is_same<I, decltype(x)>::value, "same"); // PASS
static_assert(std::is_same<decltype(begin()), decltype(x)>::value, "same"); // ERROR
}
I found a way to enable T& operator*() only when C is not constant:
template <class Tp = T>
typename std::enable_if<std::is_const<C>::value, Tp>::type const& operator* () const
{
return collection.GetAt(index);
}
template <class Tp = T>
typename std::enable_if<!std::is_const<C>::value, Tp>::type & operator* () const
{
return collection.GetAt(index);
}
I have no idea about the syntax (which I get from https://stackoverflow.com/a/26678178)
I'm trying to implement a template class that wraps around a
STL container of pointers, as in:
tContainer_t<T, vector<T*> >
or
tContainer_t<T, list<T*> >
Here's the class declaration:
template <typename T, typename Container>
class tContainer_t
{
public:
tContainer_t() {} //Default CTOR
~tContainer_t() {} //DTOR
bool IsEmpty() const;
size_t Size() const;
bool Insert(T* _element);
T* Find(T _value);
T* Remove(T _value);
T* operator[](size_t _index);
private:
tContainer_t(const tContainer_t& _other); //Disable Copy
Container m_container;
};
I want to practice different implementations for operator[] (for vector and list),so I wrote:
template <typename T, typename Container>
T* tContainer_t<T, Container>::operator[](size_t _index)
{
T* retval;
if (_index > m_container.size())
{
return 0;
}
if (typeid(m_container) == typeid(vector<T*>))
{
retval = m_container[_index];
}
if (typeid(m_container) == typeid(list<T*>))
{
typename Container::iterator contIter = m_container.begin();
advance(contIter, _index);
retval = *contIter;
}
return retval;
}
and I get strange behavior. when I use a vector in main, the code works fine. When I use a list, it doesn't even compile, showing:
container.h: In instantiation of ‘T* tContainer_t<T, Container>::operator[](size_t) [with T = int; Container = std::list<int*>; size_t = long unsigned int]’:
container.cpp:10:14: required from here
container.h:141:23: error: no match for ‘operator[]’ (operand types are ‘std::list<int*>’ and ‘size_t {aka long unsigned int}’)
retval = m_container[_index];
Here's how I'm using it:
int main(int argc, char const *argv[])
{
tContainer_t<int, list<int*> > a;
int i = 5;
a.Insert(&i);
cout << *a[0] << endl;
return 0;
}
All the code in a function will get compiled, regardless of whether or not it'll ever get run. So this:
if (typeid(m_container) == typeid(vector<T*>))
{
retval = m_container[_index];
}
cannot possibly ever work for list<T>, since list has no operator[]. Hence the compiler error. It doesn't matter that it won't get run, it still has to compile. The way to do this instead is to dispatch to different functions based on the type of m_container:
template <typename T, typename Container>
T* tContainer_t<T, Container>::operator[](size_t _index)
{
if (_index > m_container.size())
{
return 0;
}
return _get_index(m_container, _index);
}
With a vector version:
template <class T>
T* tContainer_t<T, Container>::_get_index(std::vector<T*>& v, size_t _index) {
return v[_index];
}
And a list version:
template <class T>
T* tContainer_t<T, Container>::_get_index(std::list<T*>& lst, size_t _index)
{
typename Container::iterator contIter = lst.begin();
advance(contIter, _index);
return *contIter;
}
At least that's a good general approach. In this case, we don't actually have to split them up, since we can use advance() for both container types. It'll be cheap for the vector and expensive for list:
template <typename T, typename Container>
T* tContainer_t<T, Container>::operator[](size_t _index)
{
if (_index >= m_container.size()) // note the off-by-one error I fixed here
{
return nullptr;
}
auto it = m_container.begin();
std::advance(it, _index);
return *it;
}
Or simply:
return *std::next(m_container.begin(), _index);
I have a question regarding the std::unordered_map and a custom class as it's key.
I think some background is required first:
The custom class is a variant data type, which implements basic numerical types and the std::string class.
Recently a bro of mine told me that it would be nice if the class supported arrays and hashtables. "Say no more" I thought and started implementing the array functionality (using std::vector) which works really great and then I implemented the hashmap functionality (using unordered_map<Variant, Variant>).
If I understand it right the hash function (or operator() respectively) for the unordered_map has to comply to the signature size_t (*) (const Key_Type &k) const; which my specialized version of the std::hash<Variant> object should do, shouldn't it?
In addition the unordered_map needs to check Key_Type for equality, which should be possible via operator==(), am I correct?
Anyway I'm getting a load of beautiful compiler errors of which this is, in my opinion, the most helpful:
/usr/include/c++/4.9/bits/hashtable_policy.h:85:33: error: no match for call to ‘(const std::hash<Variant>) (const Variant&)’
I really don't understand what's going on and would be really grateful for any insights in what's going on.
Below is a stripped down header of the class Variant, I hope enough information is included (to be honest I fear it's too much information but I was not sure what could be omitted).
But I left out most of the implementation details since the problem seems to occur only in the specialized hash object.
Well this is the stripped down version of the Variant header:
class Variant
{
private:
enum Type {NONE = 0, LONG, DOUBLE, STRING, ARRAY, HASH_MAP};
using Var = struct Var
{
union
{
int64_t l;
double d;
std::string *s;
std::vector<Variant> *v;
std::unordered_map<Variant, Variant> *h;
};
Type type = NONE;
};
public:
//constructors, destructor and clear function
Variant() : var() {}
Variant(long val): Variant(){var.type = LONG; var.l = val;}
Variant(double val) : Variant(){var.type = DOUBLE; var.d = val;}
Variant(const std::string &val) : Variant(){var.type = STRING; var.s = new std::string(val);}
template<typename T, typename... Args>Variant(T val, Args... args) : Variant() {set(val, args...);} //constructs an array
Variant(const Variant &val); //calls default constructor as well
Variant(Variant &&val) : Variant() {swap(*this, val);}
~Variant(){clear();}
void clear();
//set functions
template<typename T, typename... Args> void set(const T val, Args... args){if(var.type == ARRAY)var.v->clear();add(val, args...);}
void set(long val);
void set(double val);
void set(const std::string &val);
void set(const Variant &val);
//add functions
template<typename T> void add(const T val){add(Variant(val));}
template<typename T, typename... Args> void add(const T val, Args... args){add(Variant(val)); add(args...);}
void add(const std::string &val){add(Variant(val));}
void add(const Variant &val);
//array access and evaluation functions
Variant& operator[](const Variant &idx);
size_t size() const {if(var.type == ARRAY)return var.v->size(); return 0;}
std::unordered_map<Variant, Variant>::iterator begin(){if(var.type == HASH_MAP)return var.h->begin(); throw Exception("The internal type does not support iterators");}
//operator= definitions
template<typename T> Variant& operator=(const T val){set(val); return *this;}
Variant& operator=(const std::string &val){set(val); return *this;}
Variant& operator=(Variant val){swap(*this, val); return *this;}
//operator definitions
Variant& operator+=(const Variant &right);
//and operator-=, ^= etc etc...
//friend definitions (mainly comparison operators)
friend void swap(Variant &left, Variant &right); //simple swap function
friend bool operator==(const Variant &left, const Variant &right);
friend bool operator!=(const Variant &left, const Variant &right);
friend std::hash<Variant>;
private:
Var var;
};
template <typename T>
inline void hash_combine(std::size_t& seed, const T &v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
namespace std
{
template<> struct hash<Variant>
{
size_t operator()(const Variant &x) const
{
if(x.var.type == Variant::DOUBLE)
return std::hash<double>()(x.var.d);
else if(x.var.type == Variant::LONG)
return std::hash<int64_t>()(x.var.l);
else if(x.var.type == Variant::STRING)
return std::hash<std::string>()(*x.var.s);
else if(x.var.type == Variant::ARRAY)
{
size_t seed = 0;
for(size_t i = 0; i < x.var.v->size(); ++i)
hash_combine(seed, x.var.v->operator[](i));
return seed;
}
else if(x.var.type == Variant::HASH_MAP)
{
size_t seed = 0;
for(auto it = x.var.h->begin(); it != x.var.h->end(); ++it)
{
hash_combine(seed, it->first);
hash_combine(seed, it->second);
}
return seed;
}
else if(x.var.type == Variant::NONE)
return 0;
else
throw std::runtime_error("This Variant cannot be hashed");
}
};
}
inline void swap(Variant &left, Variant &right){Variant::Var tmp(left.var); left.var = right.var; right.var = tmp;}
bool operator==(const Variant &left, const Variant &right);
bool operator!=(const Variant &left, const Variant &right);
The problem here is that you use unordered_map<Variant, Variant> inside the definition of Variant itself. At this point your hash specialization is not available yet, that's why the compiler produces the error. You cannot just move hash definition before Variant definition because the hash needs access to Variant members. What you can do, is to separate declaration and definition of your hash:
class Variant;
namespace std
{
template<> struct hash<Variant>
{
size_t operator()(const Variant & x) const;
};
}
class Variant {
/* Variant definition goes here ... */
};
template <typename T>
inline void hash_combine(std::size_t& seed, const T &v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
size_t std::hash<Variant>::operator()(const Variant &x) const
{
/* hash function implementation here ... */
}
But then you have another problem: inside Variant class definition the Variant itself is incomplete type. In your union you store only pointers to vector and unordered_map, this is OK, but the begin method (actually, specification of its return type already) requires an instantiation of the unordered_map<Variant, Variant> which is not possible at that place.
(Note: Limited support for containers of incomplete types (only vector, list, and forward_list) will be added to C++17)
To solve this second problem, you can have a map member function instead of begin function which gives access to the internal map:
std::unordered_map<Variant, Variant> & map()
{
if (var.type == HASH_MAP)
return *var.h;
throw Exception("The internal type does not support iterators");
}
Then instead of
Variant v;
v.begin();
you would use
v.map().begin();
I have function template that implements storing POD into stream:
template<typename T>
void dumps(std::ostream &os, const T &t)
{
os.write(reinterpret_cast<const char *>(&t), sizeof(t));
}
Is there a way to specialize this template for containers to
store container size and then call general implementation for
container items?
This works for std::vector, std::set, and std::list. Haven't tested for any other container.
It also works for POD types. Tested with int and the following struct.
struct A
{
int a;
double b;
};
// Forward declaration of the wrapper function.
template<typename T>
void dumps(std::ostream &os, const T &t);
// Implementation for POD types
template<typename T>
void dumps(std::ostream &os, const T &t, std::true_type)
{
os.write(reinterpret_cast<const char *>(&t), sizeof(t));
}
// Implementation for container types
template<typename T>
void dumps(std::ostream &os, const T &t, std::false_type)
{
auto size = std::distance(t.begin(), t.end());
os.write(reinterpret_cast<const char *>(&size), sizeof(size));
for ( auto const& item : t)
{
dumps(os, item);
}
}
// Implementation of the wrapper function.
template<typename T>
void dumps(std::ostream &os, const T &t)
{
dumps(os, t, std::integral_constant<bool, std::is_pod<T>::value>());
}
You just need to specialise the template for each container type. For example, with a vector.
template<typename T>
void dumps(std::ostream &s, const typename std::vector<T> &t)
{
auto size = t.size();
s.write(<reinterpret_cast<const char *>(&size), sizeof(size);
for (auto const &item : t)
dumps(s, item); // using your dumps() for POD types
}
or (before C++11)
template<typename T>
void dumps(std::ostream &s, const typename std::vector<T> &t)
{
typename std::vector<T>::size_type size = t.size();
s.write(<reinterpret_cast<const char *>(&size), sizeof(size);
for (std::vector<T>::const_iterator item = t.begin(), end = t.end();
i != end; ++i)
dumps(s, *item); // using your dumps() for POD types
}
Similarly, specialise for other container types. Then it will also work for containers of containers.
Aside: this does assume you won't instantiate your templates for non-POD types. It is probably worth enforcing that (although I'll leave it as an exercise).