Overloading operator[] for a container class with template objects - c++

First of all, I have a template class that looks like this:
template <typename T>
class Configurable
{
public:
//protected:
T var_value;
std::string var_name;
std::string var_type;
Configurable()
: var_value(0), var_name("unnamed"), var_type("undefined")
{}
Configurable( T v_value, std::string v_name, std::string v_type )
: var_value(v_value), var_name(v_name), var_type(v_type)
{}
std::string get_name() {return var_name;}
};
I also have a container class named Config which has a couple of different Configurable lists for storage of Configurable ints, bools and floats. I want to overload the [] operator of Config so that it returns a Configurable with the given name (regardless of the type) from one of the lists, but this doesn't seem to work:
template <typename T>
Configurable<T>& operator[] ( const std::string v_name_arg );
The compiler returns an error of 'no match for operator[]'. So my question is - how can I make this work? Is it even possible to do it using templates or should I find a different approach with inheritance?
EDIT: Sorry for all the confusion. Here's the container class I'm talking about:
class Config
{
public:
//private:
std::list < Configurable<int> > list_int;
std::list < Configurable<float> > list_float;
std::list < Configurable<double> > list_double;
std::list < Configurable<bool> > list_bool;
//public:
Config(){}
template <typename T>
Configurable<T>& operator[] ( const std::string &v_name_arg );
};

The problem with declaring the templated operator[] without any argument that depends on the template parameter is that the compiler cannot determine the type T from a call in the form config["name"].
One solution, considering code readability, would be changing the operator[] to a method such as:
template <typename T>
Configurable<T>& get ( const std::string v_name_arg );
Then, the call should be written like:
config.get<int>("name")
Also, consider passing the string by reference (const std::string&) to avoid unnecessary copies of a std::string passed to the method/operator.

As described above, it's a syntax error.
WHen you write:
template <typename T>
Configurable<T>& operator[] ( const std::string v_name_arg );
You try to define a free standing operator[] as if it would be a free standing function.
But according to your explanations, operator[] should be a member of your container Config.
So the its definition should look somewhat like:
template <typename T>
class Config {
//...
public:
Configurable<T>& operator[] (const std::string v_name_arg) { /* return a ref to a Configurable */ };
};
With such a definition, the stuff compiles, and you can use it for example with :
int main()
{
Configurable<int> c;
Config<int> cfg;
auto a = cfg["test"];
}

Related

Generic function to get item of any std::vector containing in a given class by index

If I have a class containing only std::vector of any type as data member and I would like to make a generic function allowing me to retrieve elements by index in any vector.
What can I do to create such a function. I tried to do it using pointers to my getters and it worked but I don't know if it is really a good solution.
Is there an example of such class
class S
{
private:
std::vector<int> someIntegers;
std::vector<double> someDoubles;
std::vector<double> someDoubles2;
std::vector<int> someIntergers2;
std::vector<B> someBs;
public:
const std::vector<int>& getSomeIntegers1() const { return someIntergers; }
// and so on ...
}
My solution is to do that:
template<typename T>
std::optional<T> getByIndex(const S& s, const std::vector<T>& (S::*getCnt)() const , size_t index = 0)
{
if (const auto& cnt = (s.*getCnt)();
index >= 0 && index < cnt.size())
return cnt.at(index);
return {};
}
I'm not sure if it's a great solution due to the lack of readability.
If you want to have a generic access that routes to individual members based on the template parameter, you must define the mapping manually. However, using the template argument as selector will not support cases with multiple member vectors of the same type, for example the someDoubles and someDoubles2.
Here is a sample code to retrieve the correct vector
template <class T>
std::vector<T>& getContainer();
template <>
std::vector<int>& getContainer() {
return someIntegers;
}
template <>
std::vector<double>& getContainer() {
return someDoubles;
}
// Do so for every type you want to support

Custom comparator for custom class, just like STL

I have a custom class Binary search Tree. I want to pass a comparator class as an argument (with default being std::less). Most of the answers I searched use the STL objects and then pass their custom comparators. I want something different.
// Tree class
template <class T,class Compare = less<T>>
class Tree
{
struct TreeNode
{
T data;
struct TreeNode * left;
struct TreeNode * right;
};
public:
void insert(T);
};
// Custom comparator class
template <class T>
class CustomCompare
{
public:
bool compare(const T&, const T &);
};
template<class T>
bool CustomCompare<T>::compare(const T & a, const T &b)
{
cout << "calling custom comparator";
return a<b;
}
// inserting in tree
template<class T,class Compare>
void Tree<T,Compare>::insert(T val)
{
// HOW DO I CALL COMPARE HERE? I tried this
if (compare(val->data , treeNode->data)) /// does not work.
// I get error - use of undeclared identifier compare.
//IF I DO THIS, I get error - expected unqualified id
Compare<T> x; // cannot create instance of Compare
// IF I DO THIS< I can create instance of Compare but cannot call function compare.
Compare x;
x.compare(....) -- Error no member named compare in std::less
}
I cannot make the CustomCompare::compare static as I want the code to work for std::less too.
I hope the question is clear.
Note: I know I can overload operator < for the classes that will be using it. I am preparing for the situation in case source code of those classes is not available
std::less has the following function to compare objects.
bool operator()( const T& lhs, const T& rhs ) const;
If you want to use a custom compare class to be an equal substitute, you have to have such a function in that class too.
Then, you would use it as:
if (compare()(val->data , treeNode->data))

partial specialization of function in template class

I have a template class representing an array of numerical values.
I want this class to work for any type of numerical value (e.g. int, double, etc.) and three types of container (std::vector, std::deque, and std::list).
Here are the relevant bits of the implementation for my specific problem :
template < typename Numeric_t, typename Container = std::vector<Numeric_t> >
class Array {
// field member
Container m_data;
// other stuff here
// ...
// random element access for std::vector and std::deque
Numeric_t & operator[] (unsigned int index) { return m_data[index]; }
// random element access for std::list
Numeric_t & operator [] (unsigned int index) {
std::list<Numeric_t> :: iterator it = m_data.begin();
std::advance(it, index);
return *it;
}
}
Of course, the compiler doesn't allow me to overload the operator [].
What I would need is a kind of partial specialization for operator [] specific for std::list, but partial template function specialization is not allowed either in C++.
(I know that random element access is not efficient for a list, but that's not the point here).
Ideally, in the client code I would like to use the Array class like this :
Array < int, std::vector<int> > vec;
Array < int, std::list<int> > lst;
// fill arrays here
// ...
std::cout << vec[0] << std::endl;
std::cout << lst[0] << std::endl;
After lot of research I was not able to find a working solution.
What would be the most elegant way to solve this problem ?
Thanks for your help.
A clean solution is to use full-class template specialization. The different specializations can be derived form one common base class, in order to share common code.
Write a class ArrayBase containing all the code that does not depend on the particular container type and that grants access to the container, by making it protected or making Array a friend class.
template <class Numeric_t, class Container>
class Array
: public ArrayBase<Numeric_t, Container>
{
// Container specific code, generic version that works for all containers.
};
template <class Numeric_t>
class Array<Numeric_t, std::vector<Numeric_t>>
: public ArrayBase<Numeric_t, std::vector<Numeric_t>>
{
// Optimized code for std::vector.
}
Another approach: You can also write a static member function containing code to access the idx-th entry of the container and specialize that function:
template <class Numeric_t, class Container>
class Array
{
template <class Cont>
static Numeric_t get(Cont const& container, unsigned int idx)
{
std::list<Numeric_t>::iterator it = container.begin();
std::advance(it, idx);
return *it;
}
template <>
static Numeric_t get(std::vector<Numeric_t> const& container, unsigned int idx)
{
return container[idx];
}
public:
Numeric_t operator[](unsigned int idx) const { return get(m_data, idx); }
};
I am sorry, this does not work. I forgot that you can't specialize static member functions ... again.
Another alternative is to use SFINAE, but it is a non-idiomatic use of it and I would not recommend it in this case.

Switch template type

I want to make some storage for my game. Now the code looks like:
class WorldSettings
{
private:
std::map<std::string, int> mIntegerStorage;
std::map<std::string, float> mFloatStorage;
std::map<std::string, std::string> mStringStorage;
public:
template <typename T>
T Get(const std::string &key) const
{
// [?]
}
};
So, I have a few associative containers which stores the exact type of data. Now I want to add into settings some value: settings.Push<int>("WorldSize", 1000); and get it: settings.Get<int>("WorldSize");. But how to switch need map due to passed type into template?
Or, maybe, you know a better way, thanks.
If your compiler supports this1, you can use template function specialisations:
class WorldSettings
{
private:
std::map<std::string, int> mIntegerStorage;
std::map<std::string, float> mFloatStorage;
std::map<std::string, std::string> mStringStorage;
public:
template <typename T>
T Get(const std::string &key); // purposely left undefined
};
...
template<>
int WorldSettings::Get<int>(const std::string& key) {
return mIntegerStorage[key];
}
template<>
float WorldSettings::Get<float>(const std::string& key) {
return mFloatStorage[key];
}
// etc
Notice that the methods are not const because map<>::operator[] is not const.
Also, if someone tries to use the template with a type other than one you have provided a specialisation for, they will get linker errors, so your code won't misbehave or anything. Which is optimal.
1 If not, see #gwiazdorrr's answer
First of all, since prior to C++11 you can't specialise functions, your member functions must differ in signature - return type does not count. From my experience on some compilers you can do without it, but as usual - you should keep your code as close to standard as possible.
That said you can add a dummy paramater that won't affect performance and the way you call function:
public:
template <typename T>
T Get(const std::string &key) const
{
return GetInner(key, (T*)0);
}
private:
int GetInner(const std::string& key, int*) const
{
// return something from mIntegerStorage
}
float GetInner(const std::string& key, float*) const
{
// return something from mFloatStorage
}
And so on. You get the idea.
Seth's answer is ideal, but if you don't have access to a C++11 compiler, then you can use template class specialization and do this instead. It's much more verbose, but keeps the same functionality.
class WorldSettings
{
template<class T>
struct Selector;
template<class T>
friend struct Selector;
private:
std::map<std::string, int> mIntegerStorage;
std::map<std::string, float> mFloatStorage;
std::map<std::string, std::string> mStringStorage;
public:
template <typename T>
T Get(const std::string &key)
{
return Selector<T>::Get(*this)[key];
}
};
template<>
struct WorldSettings::Selector<int>
{
static std::map<std::string, int> & Get(WorldSettings &settings)
{
return settings.mIntegerStorage;
}
};
template<>
struct WorldSettings::Selector<float>
{
static std::map<std::string, float> & Get(WorldSettings &settings)
{
return settings.mFloatStorage;
}
};
// etc.
In C++03 I would recommend the use of ‘boost::any‘ in the type of the container, and the. You need a single accessor:
std::map<std::string,boost::any> storage;
template <typename T> getValue( std::string const & key ) {
return boost::any_cast<T>( storage[key] );
}
This is a rough sketch, as a member function It would be const, and it should use ‘map::find‘ not to modify the container when searching, it should deal with invalid joeys, and probably remap the boost exceptions into your own application exceptions.

Enforcing different C++ template arguments

I'm try to create a BidirectionalMap class using (only) STL (no, boost is not an option.) I have 99% percent of it working the way that I want, but what I really can't figure out is how to force the template to require two different types so that operator[] can be properly overridden. Something like...
template < class KeyType, class ValueType >
class BidirectionalMap
{
public:
...
const ValueType& operator[](const KeyType& _k ) { return( m_keyMap[ _k ] ); }
const KeyType& operator[](const ValueType& _v ) { return( m_valMap[ _v ] ); }
private:
std::map< KeyType > m_keyMap;
std::map< ValueType > m_valueMap;
};
main()
{
BidirectionalMap< Foo, Foo > fooMap; // won't work, ambiguous.
BidirectionalMap< Foo, Bar > fooBarMap; // does work.
}
Thoughts?
-R
Just add the following partial specialisation:
template <typename T>
class BidirectionalMap<T, T>;
This will case the compiler to instantiate a template that isn’t defined (since the above is only declared) and bail if the user tries to pass the same type as both template arguments.
Of course the real question is: Why such an arbitrary restriction ?
I would consider perfectly normal to have the same type as key and value, so instead of providing an ambiguous operator overload, perhaps could you simply provide two different methods ?
ValueType const& by_key(KeyType const&) const;
KeyType const& by_value(ValueType const&) const;
and be done with it.
EDIT: Following #Georg Fritzsche's remark :)
Remember that one of the paramount rules of overloading is that all overloads should have the same basic meaning.