C++ [] mapping, possibly through templates - c++

I have a little problem in C++ I don't know how to solve.
The first part of the problem is to access an element in a struct via [], or better, to map [] to a subelement.
My struct looks like this:
struct e {
std::string content;
std::string name;
std::map<std::string, std::vector<e> > elements;
};
If I want to access a subelement of e, I can do this like this: e.elements["e1"][0].elements["e1sub"][0].content, would it be possible to map this so I can call it like this: e["e1"][0]["e1sub"][0], this would just mean that he has to "replace" every e[] with e.elements[].
Maybe this can be done with templates but I don't know how to use them yet as I'm just beginning to learn C++.
Thanks in advance for any help, Robin.

You need to overload operator[]. Typically, you want to implement two versions of that operator, but since std::map only overloads the non-const version, that might be enough for you.
Something like the following should do:
struct e {
std::string content;
std::string name;
std::map<std::string, std::vector<e> > elements;
std::vector<e>& operator[](const std::string& key) {return elements[key];}
};

You can "overload" the [] operator, try:
struct e {
std::string content;
std::string name;
std::map<std::string, std::vector<e> > elements;
std::vector<e>& operator [](const std::string& s);
};
...
std::vector<e>& e::operator [](const std::string& s) {
return elements[s];
}

You don't need templates. You merely need an operator[] :
std::vector<e>& e::operator[](std::string const& s) { return this->elements[s]; }
// elements.operator[s] inserts s if it doesn't exist yet. That's non-const so the following won't work
// std::vector<e> const& e::operator[](std::string const& s) const { return this->elements[s]; }

Related

Can a map's key be shared with part of the value?

Can a std::map's or std::unordered_map's key be shared with part of the value? Especially if the key is non-trivial, say like a std::string?
As a simple example let's take a Person object:
struct Person {
// lots of other values
std::string name;
}
std::unordered_map<std::string, std::shared_ptr<Person>> people;
void insertPerson(std::shared_ptr<Person>& p) {
people[p.name] = p;
// ^^^^^^
// copy of name string
}
std::shared_ptr<Person> lookupPerson(const std::string& name) const {
return people[name];
}
My first thought is a wrapper around the name that points to the person, but I cannot figure out how to do a lookup by name.
For your purpose, a std::map can be considered a std::set containing std::pair's which is ordered (and thus efficiently accessible) according to the first element of the pair.
This view is particularly useful if key and value elements are partly identical, because then you do not need to artificially separate value and key elements for a set (and neither you need to write wrappers around the values which select the key).
Instead, one only has to provide a custom ordering function which works on the set and extracts the relevant key part.
Following this idea, your example becomes
auto set_order = [](auto const& p, auto const& s) { return p->name < s->name; };
std::set<std::shared_ptr<Person>, decltype(set_order)> people(set_order);
void insertPerson(std::shared_ptr<Person>& p) {
people.insert(p);
}
As an alternative, here you could also drop the custom comparison and order the set by the addresses in the shared pointer (which supports < and thus can be used directly in the set):
std::set<std::shared_ptr<Person> > people;
void insertPerson(std::shared_ptr<Person>& p) {
people.insert(p);
}
Replace set by unordered_set where needed (in general you then also need to provide a suitable hash function).
EDIT: The lookup can be performed using std:lower_bound:
std::shared_ptr<Person> lookupPerson(std::string const& s)
{
auto comp = [](auto const& p, auto const& s) { return p->name < s; };
return *std::lower_bound(std::begin(people), std::end(people), s, comp);
}
DEMO.
EDIT 2: However, given this more-or-less ugly stuff, you can also follow the lines of your primary idea and use a small wrapper around the value as key, something like
struct PersonKey
{
PersonKey(std::shared_ptr<Person> const& p) : s(p->name) {}
PersonKey(std::string const& _s) : s(_s) {}
std::string s;
bool operator<(PersonKey const& rhs) const
{
return s < rhs.s;
}
};
Use it like (untested)
std::map<PersonKey, std::shared_ptr<Person> > m;
auto sptr = std::make_shared<Person>("Peter");
m[PersonKey(sptr)]=sptr;
Lookup is done through
m[PersonKey("Peter")];
Now I like this better than my first suggestion ;-)
Here's an alternative to davidhigh's answer.
struct Person {
// lots of other values
std::string name;
}
struct StrPtrCmp {
bool operator()(const std::string* a, const std::string* b) const {
return *a < *b;
}
}
std::map<const std::string*, std::shared_ptr<Person>, StrPtrCmp> people();
void insertPerson(std::shared_ptr<Person>& p) {
people[&(p.name)] = p;
}
std::shared_ptr<Person> lookupPerson(const std::string& name) const {
return people[&name];
}
And a few edits to make it work with std::unordered_map:
struct StrPtrHash {
size_t operator()(const std::string* p) const {
return std::hash<std::string>()(*p);
}
};
struct StrPtrEquality {
bool operator()(const std::string* a, const std::string* b) const {
return std::equal_to<std::string>()(*a, *b);
}
};
std::unordered_map<const std::string*, std::shared_ptr<Person>, StrPtrHash, StrPtrEquality> people();

How to make aliases of member function or variable of specific class(like an STL container)

When using a std::pair or std::map, we need to use "first" or "second" to access data. But the two variable name do not have clear meanings of what it really store for other co-workers that did not write this code. So if we can make aliases for "first" or "second", it would enhance much readability.
For example, the following code
static const std::map<std::string, std::pair<std::string, PFConvert>> COMM_MAP =
{ // keyword-> (caption, function)
{std::string("1"), {std::string("Big5 to Utf16LE"), &FileConvert_Big5ToUtf16LE}},
{std::string("2"), {std::string("Utf16LE to Utf8"), &FileConvert_Utf16LEToUtf8}},
{std::string("3"), {std::string("Utf8 to Big5"), &FileConvert_Utf8ToBig5}}
};
auto iterToExe = COMM_MAP.find(strTransType);
iterToExe->second.second();
The iterToExe->second.second(); has a truly bad readability.
So I try to use inherit to give aliases as following
template<typename PFComm>
class CCommContent : public std::pair<std::string, PFComm>
{
public:
std::string &strCaption = std::pair<std::string, PFComm>::first;
PFComm &pfComm = std::pair<std::string, PFComm>::second;
};
template<typename PFComm>
class CCommPair : public std::pair<std::string, CCommContent<PFComm>>
{
public:
std::string &strPattern = std::pair<std::string, CCommContent<PFComm>>::first;
CCommContent<PFComm> commContent = std::pair<std::string,CCommContent<PFComm>>::second;
};
template<typename PFComm>
class CCommMap : public std::map<std::string, CCommContent<PFComm>, std::less<std::string>, std::allocator<CCommPair<PFComm>>>
{};
But this comes to an another issue: I have to declare all the ctors, though i could call the base ctors, but it still not seems to be a smart method. I Just want to make aliases.
A simple way is to use macro ...... but it bypass the type checking. when using a nested structure, it may be a nightmare when debug.
Any advice or discussion would be appreciated.
Why not simply use your own struct with your own element names?
struct MyPair {
std::string strCaption;
PFComm pfComm;
};
With C++11 you can easily create new objects of it:
MyPair{std::string("Big5 to Utf16LE"), &FileConvert_Big5ToUtf16LE}}
And if you define your own operator<, you can have std::set work as a map:
bool operator<(const MyPair& a, const MyPair& b) {
return a.strCaption < b.strCaption;
}
typedef std::set<MyPair> MyPairMap;
Naturally, you can nest your custom structs to form more complex nested pairs, although in your case you might want to consider a flat triplet instead:
struct CommMapEntry {
std::string number;
std::string caption;
PFComm pfComm;
};
bool operator<(const MyPair& a, const MyPair& b) {
return a.number<b.number;
}
static const std::set<CommMapEntry> COMM_MAP;
How about some typedefs and accessor functions?
using CommEntry = std::pair<std::string, PFConvert>;
std::string const & getCaption(CommEntry const & e) { return e.first; }
PFConvert const & getFunction(CommEntry const & e) { return e.second; }
Now you can say:
auto it = COMM_MAP.find(strTransType);
if (it != COMM_MAP.end())
{
auto & c = getCaption(it->second);
auto & l = getLabel(it->second);
// ...
}
If you later change the details of the type, you just have adapt the accessor functions.
well, in c++11, we can using base::base in a derive class to use the base ctors. But note that vs2013 DO NOT compliant this. g++4.8 do.

how to copy an attribute of an object to a vector?

I have a vector<Person> and want to extract the attribute name of each object, to a vector<string>. The for-each loop does it job correctly, but is there a nicer way to use something like std::copy() and tell him to use a specific method (in this case the name() getter) to copy the values into my vector<string>?
class Person {
int a;
string n;
public:
Person(int age, string name) : a(age), n(name) {}
int age() const { return a; }
string name() const { return n; }
};
vector<Person> ppl = { Person(12, "Tim"), Person(21, "Tom") };
vector<string> names;
// question
// copy(ppl.begin(), ppl.end(), ????.name());
for (Person& p : ppl)
names.push_back(p.name());
copy(names.begin(), names.end(), ostream_iterator<string>(cout, ", "));
How about std::transform with std::back_inserter:
std::transform(std::begin(ppl), std::end(ppl),
std::back_inserter(names),
[](const Person& p) -> std::string { return p.name(); });
In the end though, it's still basically the same as what you already have.
A range-based for loop is usually the most straightforward way to do an arbitrary operation to each element of a container. There's also the transform algorithm:
transform(
ppl.begin(),
ppl.end(),
back_inserter(names),
[](Person const & p){return p.name();}
);
vector<string> names(ppl.size());
transform(ppl.begin(), ppl.end(), names.begin(), [](const Person& p) { return p.name(); });
or for C++03:
transform(ppl.begin(), ppl.end(), names.begin(), mem_fun(&Person::name));

Finding in a std::vector of structures

I have:
struct MyStruct
{
char* name;
int* somethingElse;
};
And I need to find in a std::vector<MyStruct*> an element (by using std::find_if) whose name is "XYZ" ... but ... the Predicate of std::find_if (if I have managed to understand it correctly) is a plain function, and it takes in a MyStruct pointer and I have no idea where I can specify the extra "XYZ" value to be used in the comparison.
So, how can I use std::find_if or this purpose? (Obviously, looking for a nice solution, not a global variable, or just walk through the list, ....)
Thanks, f
You can use a functor for this (hope I didn't get anything wrong, as I typed it in the browser):
class finder
{
const char* name;
public:
finder(const char* _name): name(_name) {}
bool operator()(MyStruct* elem) {return strcmp(elem->name, name) == 0;}
};
finder f("sample");
std::find_if(myvector.begin(), myvector.end(), f);
If you use C++11 and lambda:
std::vector<MyStruct> mystructus;
std::find_if(mystructus.begin(), mystructus.end(),
[](const MyStruct& ms){ return ms.name == std::string("XYZ"); } );
You have two options, either use functors or lamdas.
Using functors, you create a new class (or structure) whose constructor takes the string you want to search for, then it has an operator() function that is called by std::find_if:
class my_finder
{
std::string search;
public:
my_finder(const std::string& str)
: search(str)
{}
bool operator()(const MyStruct* my_struct) const
{ return search == my_struct->name; }
};
// ...
std::find_if(std::begin(...), std::end(...), my_finder("XYZ"));
The second using lambdas is less code, but requires recent version of the compiler that can handle C++11 lambdas:
std::find_if(std::begin(...), std::end(...), [](const MyStruct* my_struct)
{ return std::string("XYZ") == my_struct->name; });
The last example can even be generalized further:
using namespace std::placeholders; // For `_1` used below in `std::bind`
// Declare a "finder" function, to find your structure
auto finder = [](const MyStruct* my_struct, const std::string& to_find) {
return to_find == my_struct->name;
};
auto xyz = std::find_if(std::begin(...), std::end(...), std::bind(finder, _1, "XYZ"));
auto abc = std::find_if(std::begin(...), std::end(...), std::bind(finder, _1, "ABC"));
This way the lambda can be reused.
Predicate is anything, that can have operator () applied to it (with the expected argument(s) and returns something convertible to bool). A pointer to function is such thing, but so is an object that defines operator().
You need to provide a predicate like this:
struct Comparator
{
Comparator(const char* find) : m_find(find){}
bool operator()(MyStruct* p) const
{
return strcmp(p->name, m_find) == 0;
}
const char* m_find;
};
Then you can std::find_if like this:
vector<MyStruct*>::iterator iter = std::find_if(vec.begin(), vec.end(), Comparator("XYZ"));
if(iter != vec.end())
{
MyStruct* p = *iter;
}
Or if your compiler supports C++11 you can use lambdas and get rid of the predicate functor:
auto it = std::find_if(vec.begin(), vec.end(), [](MyStruct* p) { return strcmp(p->name, "XYZ") == 0;});

C++ std::find with a custom comparator

This is basically what I want to do:
bool special_compare(const string& s1, const string& s2)
{
// match with wild card
}
std::vector<string> strings;
strings.push_back("Hello");
strings.push_back("World");
// I want this to find "Hello"
find(strings.begin(), strings.end(), "hell*", special_compare);
// And I want this to find "World"
find(strings.begin(), strings.end(), "**rld", special_compare);
But std::find doesn't work like that unfortunately. So using only the STL, how can I do something like this?
Based on your comments, you're probably looking for this:
struct special_compare : public std::unary_function<std::string, bool>
{
explicit special_compare(const std::string &baseline) : baseline(baseline) {}
bool operator() (const std::string &arg)
{ return somehow_compare(arg, baseline); }
std::string baseline;
}
std::find_if(strings.begin(), strings.end(), special_compare("hell*"));
The function you need to use is this : std::find_if, because std::find doesn't take compare function.
But then std::find_if doesn't take value. You're trying to pass value and compare both, which is confusing me. Anyway, look at the documentation. See the difference of the usage:
auto it1 = std::find(strings.begin(), strings.end(), "hell*");
auto it2 = std::find_if(strings.begin(), strings.end(), special_compare);
Hope that helps.
You'll need std::find_if(), which is awkward to use, unless you're on a C++11 compiler. Because then, you don't need to hardcode the value to search for in some comparator function or implement a functor object, but can do it in a lambda expression:
vector<string> strings;
strings.push_back("Hello");
strings.push_back("World");
find_if(strings.begin(), strings.end(), [](const string& s) {
return matches_wildcard(s, "hell*");
});
Then you write a matches_wildcard() somewhere.
Since nobody has mentioned std::bind yet, I'll propose this one
#include <functional>
bool special_compare(const std::string& s, const std::string& pattern)
{
// match with wild card
}
std::vector<std::string> strings;
auto i = find_if(strings.begin(), strings.end(), std::bind(special_compare, std::placeholders::_1, "hell*"));
With C++11 lambdas:
auto found = find_if(strings.begin(), strings.end(), [] (const std::string& s) {
return /* you can use "hell*" here! */;
});
If you can't use C++11 lambdas, you can just make a function object yourself. Make a type and overload operator ().
I wanted to have an example with custom class having custom find logic but didn't find any answer like that. So I wrote this answer which uses custom comparator function (C++11) to find an object.
class Student {
private:
long long m_id;
// private fields
public:
long long getId() { return m_id; };
};
Now suppose, I want to find the student object whose m_id matches with a given id. I can write std::find_if like this:
// studentList is a vector array
long long x_id = 3; // local variable
auto itr = std::find_if(studentList.begin(), studentList.end(),
[x_id](Student& std_val)
{ return std_val.getId() == x_id; }
);
if(itr == studentList.end())
printf("nothing found");