Let's see a real life example:
class RuleNameConverter {
public:
RuleNameConverter(const boost::property_tree::ptree& pt);
int toIdentifier(const std::string& name) const;
std::string toName(const int id) const;
private:
using Bimap = boost::bimap<std::string, int>;
Bimap bimap_;
};
Where the constructor is this:
RuleNameConverter::RuleNameConverter(const boost::property_tree::ptree& pt) {
for (const auto& item : pt) {
if (item.first == "rule") {
auto name = item.second.get < std::string > ("<xmlattr>.name");
auto id = item.second.get<int>("<xmlattr>.id");
bimap_.insert(Bimap::value_type { name, id });
}
}
}
Assume you want a const member attribute:
...
const Bimap bimap_;
};
You must initialize it in the initializer list, not in the constructor body. It's initialization is non trivial, so you must delegate a function to compute its value. You can use the value returned by a lambda, taking advantages of the move semantics (no copy of temporary objects):
RuleNameConverter::RuleNameConverter(const boost::property_tree::ptree& pt) :
bimap_ { [&pt]() {
Bimap results;
for (const auto& item : pt) {
if (item.first == "rule") {
auto name = item.second.get < std::string > ("<xmlattr>.name");
auto id = item.second.get<int>("<xmlattr>.id");
results.insert(Bimap::value_type {name, id});
}
}
return results;
}() } {
}
Are there any drawbacks to using this technique? Is it worth the trouble? I find it slightly less readable, but what about performance?
Performance-wise, it should not matter all that much. You don't copy around any Bitmap objects, and the construction of your lambda should not take any noticeable time.
But for readability, I would create a static member function instead of a lambda here:
class RuleNameConverter {
public:
RuleNameConverter(const boost::property_tree::ptree& pt);
private:
static Bitmap createBitmap(const boost::property_tree::ptree& pt);
};
RuleNameConverter::RuleNameConverter(const boost::property_tree::ptree& pt) :
bimap_ { createBitmap(pt) } {
}
Bitmap RuleNameConverter::createBitmap(const boost::property_tree::ptree& pt) {
Bimap results;
for (const auto& item : pt) {
if (item.first == "rule") {
auto name = item.second.get < std::string > ("<xmlattr>.name");
auto id = item.second.get<int>("<xmlattr>.id");
results.insert(Bimap::value_type {name, id});
}
}
return results;
}
When you need to initialise several members using helper functions, creating a new lambda for each member leads to an unmaintainable mess in the constructor initialiser list, but several helper functions don't need to have that problem. Additionally, if you add constructor overloads, createBitmap can be easily called from multiple constructors.
Alternatively, use a regular non-member function if the body of createBitmap is not really specific to your RuleNameConverter.
You could wrap the Bimap in another class, where its constructor would have the exact same body as the lambda.
I can't see how using a lambda to avoid a superficial class in this case would lead to any problems, except its intent is perhaps less clear, because it doesn't have a name (but that's the case with pretty much any lambda).
Related
I have the following class:
class Document
{
public:
Document():
// default values for members,
// ...
m_dirty{false}{}
// Accessor functions
template<class OutputStream>
Document& save(OutputStream stream)
{
// Write stuff to `stream`
// ...
m_dirty = false;
return *this;
}
bool dirty() const { return m_dirty; }
private:
Size2d m_canvas_size;
LayerStack m_layers;
LayerIndex m_current_layer;
std::vector<Palette> m_palettes;
PaletteIndex m_current_palette;
ColorIndex m_current_color;
std::vector<std::string> m_palette_names;
std::vector<std::string> m_layer_names;
bool m_dirty;
};
Should the class have public member functions for modifying an element of say m_palettes directly, like
Document& color(PaletteIndex, ColorIndex, Color)
, or is it more "correct", to only allow access to the entire vector, through a pair of API:s
std::vector<Palette> const& palettes();
Document& palettes(std::vector<Palette>&&);
The first option would be more efficient, since it would not require to create a temporary copy of the data member, but consistent use of this design would make the interface bloated. It would require "deep" getters and setters for every container in the class.
Notice the dirty flag. Thus, the following would break the abstraction:
std::vector<Palette>& palettes();
You might have Proxy to "propagate" dirty flag from Palette modification, something like:
template <typename T>
class DirtyProxy
{
T& data;
bool& dirty;
public:
DirtyProxy(T& data, bool& dirty) : data(data), dirty(dirty) {}
~DirtyProxy() { dirty = true;}
DirtyProxy(const DirtyProxy&) = delete;
T* operator ->() { return data; }
};
And then
DirtyProxy<Palette> palette(std::size_t i) { return {m_palettes.at(i), dirty}; }
I think the most robust way to solve it is to use a a callback. An issue with the proxy is that it would not handle the case where the the client code throws an exception (assuming strong exception guarantee). Testcase:
try
{
auto property_proxy = obj.getProperty();
// an exception is thrown here...
property_proxy->val = x; // Never updated
}
catch(...)
{}
assert(!obj.dirty());
will fail, because the dtor always sets the dirty flag. However with a callback
class Foo
{
public:
template<class F>
Foo& modifyInSitu(F&& f)
{
f(x);
m_dirty = true;
return *this
}
};
will only update m_dirty, when f(x) does not throw.
My problem comes from a project that I'm supposed to finish. I have to create an std::unordered_map<T, unsigned int> where T is a pointer to a base, polymorphic class. After a while, I figured that it will also be a good practice to use an std::unique_ptr<T> as a key, since my map is meant to own the objects. Let me introduce some backstory:
Consider class hierarchy with polymorphic sell_obj as a base class. book and table inheriting from that class. We now know that we need to create a std::unordered_map<std::unique_ptr<sell_obj*>, unsigned int>. Therefore, erasing a pair from that map will automatically free the memory pointed by key. The whole idea is to have keys pointing to books/tables and value of those keys will represent the amount of that product that our shop contains.
As we are dealing with std::unordered_map, we should specify hashes for all three classes. To simplify things, I specified them in main like this:
namespace std{
template <> struct hash<book>{
size_t operator()(const book& b) const
{
return 1; // simplified
}
};
template <> struct hash<table>{
size_t operator()(const table& b) const
{
return 2; // simplified
}
};
// The standard provides a specilization so that std::hash<unique_ptr<T>> is the same as std::hash<T*>.
template <> struct hash<sell_obj*>{
size_t operator()(const sell_obj *s) const
{
const book *b_p = dynamic_cast<const book*>(s);
if(b_p != nullptr) return std::hash<book>()(*b_p);
else{
const table *t_p = static_cast<const table*>(s);
return std::hash<table>()(*t_p);
}
}
};
}
Now let's look at implementation of the map. We have a class called Shop which looks like this:
#include "sell_obj.h"
#include "book.h"
#include "table.h"
#include <unordered_map>
#include <memory>
class Shop
{
public:
Shop();
void add_sell_obj(sell_obj&);
void remove_sell_obj(sell_obj&);
private:
std::unordered_map<std::unique_ptr<sell_obj>, unsigned int> storeroom;
};
and implementation of two, crucial functions:
void Shop::add_sell_obj(sell_obj& s_o)
{
std::unique_ptr<sell_obj> n_ptr(&s_o);
storeroom[std::move(n_ptr)]++;
}
void Shop::remove_sell_obj(sell_obj& s_o)
{
std::unique_ptr<sell_obj> n_ptr(&s_o);
auto target = storeroom.find(std::move(n_ptr));
if(target != storeroom.end() && target->second > 0) target->second--;
}
in my main I try to run the following code:
int main()
{
book *b1 = new book("foo", "bar", 10);
sell_obj *ptr = b1;
Shop S_H;
S_H.add_sell_obj(*ptr); // works fine I guess
S_H.remove_sell_obj(*ptr); // usually (not always) crashes [SIGSEGV]
return 0;
}
my question is - where does my logic fail? I heard that it's fine to use std::unique_ptr in STL containters since C++11. What's causing the crash? Debugger does not provide any information besides the crash occurance.
If more information about the project will be needed, please point it out. Thank you for reading
There are quite a few problems with logic in the question. First of all:
Consider class hierarchy with polymorphic sell_obj as base class. book and table inheriting from that class. We now know that we need to create a std::unordered_map<std::unique_ptr<sell_obj*>, unsigned int>.
In such cases std::unique_ptr<sell_obj*> is not what we would want. We would want std::unique_ptr<sell_obj>. Without the *. std::unique_ptr is already "a pointer".
As we are dealing with std::unordered_map, we should specify hashes for all three classes. To simplify things, I specified them in main like this: [...]
This is also quite of an undesired approach. This would require changing that part of the code every time we add another subclass in the hierarchy. It would be best to delegate the hashing (and comparing) polymorphically to avoid such problems, exactly as #1201programalarm suggested.
[...] implementation of two, crucial functions:
void Shop::add_sell_obj(sell_obj& s_o)
{
std::unique_ptr<sell_obj> n_ptr(&s_o);
storeroom[std::move(n_ptr)]++;
}
void Shop::remove_sell_obj(sell_obj& s_o)
{
std::unique_ptr<sell_obj> n_ptr(&s_o);
auto target = storeroom.find(std::move(n_ptr));
if(target != storeroom.end() && target->second > 0) target->second--;
}
This is wrong for couple of reasons. First of all, taking an argument by non-const reference suggest modification of the object. Second of all, the creation of n_ptr from a pointer obtained by using & on an argumnet is incredibly risky. It assumes that the object is allocated on the heap and it is unowned. A situation that generally should not take place and is incredibly dangerous. In case where the passed object is on the stack and / or is already managed by some other owner, this is a recipe for a disaster (like a segfault).
What's more, it is more or less guaranteed to end up in a disaster, since both add_sell_obj() and remove_sell_obj() create std::unique_ptrs to potentially the same object. This is exactly the case from the original question's main(). Two std::unique_ptrs pointing to the same object result in double delete.
While it's not necessarily the best approach for this problem if one uses C++ (as compared to Java), there are couple of interesting tools that can be used for this task. The code below assumes C++20.
The class hierarchy
First of all, we need a base class that will be used when referring to all the objects stored in the shop:
struct sell_object { };
And then we need to introduce classes that will represent conrete objects:
class book : public sell_object {
std::string title;
public:
book(std::string title) : title(std::move(title)) { }
};
class table : public sell_object {
int number_of_legs = 0;
public:
table(int number_of_legs) : number_of_legs(number_of_legs) { }
};
For simplicity (but to still have some distinctions) I chose for them to have just one, distinct field (title and number_of_legs).
The storage
The shop class that will represent storage for any sell_object needs to somehow store, well, any sell_object. For that we either need to use pointers or references to the base class. You can't have a container of references, so it's best to use pointers. Smart pointers.
Originally the question suggested the usage of std::unordered_map. Let us stick with it:
class shop {
std::unordered_map<
std::unique_ptr<sell_object>, int,
> storage;
public:
auto add(...) -> void {
...
}
auto remove(...) -> void {
...
}
};
It is worth mentioning that we chose std::unique_ptr as key for our map. That means that the storage is going to copy the passed objects and use the copies it owns to compare with elements we query (add or remove). No more than one equal object will be copied, though.
The fixed version of storage
There is a problem, however. std::unordered_map uses hashing and we need to provide a hash strategy for std::unique_ptr<sell_object>. Well, there already is one and it uses the hash strategy for T*. The problem is that we want to have custom hashing. Those particular std::unique_ptr<sell_object>s should be hashed according to the associated sell_objects.
Because of this, I opt to choose a different approach than the one proposed in the question. Instead of providing a global specialization in the std namespace, I will choose a custom hashing object and a custom comparator:
class shop {
struct sell_object_hash {
auto operator()(std::unique_ptr<sell_object> const& object) const -> std::size_t {
return object->hash();
}
};
struct sell_object_equal {
auto operator()(
std::unique_ptr<sell_object> const& lhs,
std::unique_ptr<sell_object> const& rhs
) const -> bool {
return (*lhs <=> *rhs) == 0;
}
};
std::unordered_map<
std::unique_ptr<sell_object>, int,
sell_object_hash, sell_object_equal
> storage;
public:
auto add(...) -> void {
...
}
auto remove(...) -> void {
...
}
};
Notice a few things. First of all, the type of storage has changed. No longer it is an std::unordered_map<std::unique_ptr<T>, int>, but an std::unordered_map<std::unique_ptr<T>, int, sell_object_hash, sell_object_equal>. This is to indicate that we are using custom hasher (sell_object_hash) and custom comparator (sell_object_equal).
The lines we need to pay extra attention are:
return object->hash();
return (*lhs <=> *rhs) == 0;
Onto them:
return object->hash();
This is a delegation of hashing. Instead of being an observer and trying to have a type that for each and every possible type derived from sell_object implements a different hashing, we require that those objects supply the sufficient hashing themselves. In the original question, the std::hash specialization was the said "observer". It certainly did not scale as a solution.
In order to achieve the aforementioned, we modify the base class to impose the listed requirement:
struct sell_object {
virtual auto hash() const -> std::size_t = 0;
};
Thus we also need to change our book and table classes:
class book : public sell_object {
std::string title;
public:
book(std::string title) : title(std::move(title)) { }
auto hash() const -> std::size_t override {
return std::hash<std::string>()(title);
}
};
class table : public sell_object {
int number_of_legs = 0;
public:
table(int number_of_legs) : number_of_legs(number_of_legs) { }
auto hash() const -> std::size_t override {
return std::hash<int>()(number_of_legs);
}
};
return (*lhs <=> *rhs) == 0;
This is a C++20 feature called the three-way comparison operator, sometimes called the spaceship operator. I opted into using it, since starting with C++20, most types that desire to be comparable will be using this operator. That means we also need our concrete classes to implement it. What's more, we need to be able to call it with base references (sell_object&). Yet another virtual function (operator, actually) needs to be added to the base class:
struct sell_object {
virtual auto hash() const -> std::size_t = 0;
virtual auto operator<=>(sell_object const&) const -> std::partial_ordering = 0;
};
Every subclass of sell_object is going to be required to be comparable with other sell_objects. The main reason is that we need to compare sell_objects in our storage map. For completeness, I used std::partial_ordering, since we require every sell_object to be comparable with every other sell_object. While comparing two books or two tables yields strong ordering (total ordering where two equivalent objects are indistinguishable), we also - by design - need to support comparing a book to a table. This is somewhat meaningless (always returns false). Fortunately, C++20 helps us here with std::partial_ordering::unordered. Those elements are not equal and neither of them is greater or less than the other. Perfect for such scenarios.
Our concrete classes need to change accordingly:
class book : public sell_object {
std::string title;
public:
book(std::string title) : title(std::move(title)) { }
auto hash() const -> std::size_t override {
return std::hash<std::string>()(title);
}
auto operator<=>(book const& other) const {
return title <=> other.title;
};
auto operator<=>(sell_object const& other) const -> std::partial_ordering override {
if (auto book_ptr = dynamic_cast<book const*>(&other)) {
return *this <=> *book_ptr;
} else {
return std::partial_ordering::unordered;
}
}
};
class table : public sell_object {
int number_of_legs = 0;
public:
table(int number_of_legs) : number_of_legs(number_of_legs) { }
auto hash() const -> std::size_t override {
return std::hash<int>()(number_of_legs);
}
auto operator<=>(table const& other) const {
return number_of_legs <=> other.number_of_legs;
};
auto operator<=>(sell_object const& other) const -> std::partial_ordering override {
if (auto table_ptr = dynamic_cast<table const*>(&other)) {
return *this <=> *table_ptr;
} else {
return std::partial_ordering::unordered;
}
}
};
The overriden operator<=>s are required due to the base class' requirements. They are quite simple - if the other object (the one we are comparing this object to) is of the same type, we delegate to the <=> version that uses the concrete type. If not, we have a type mismatch and we report the unordered ordering.
For those of you who are curious why the <=> implementation that compares two, identical types is not = defaulted: it would use the base-class comparison first, which would delegate to the sell_object version. That would dynamic_cast again and delegate to the defaulted implementation. Which would compare the base class and... result in an infinite recursion.
add() and remove() implementation
Everything seems great, so we can move on to adding and removing items to and from our shop. However, we immediately arrive at a hard design decision. What arguments should add() and remove() accept?
std::unique_ptr<sell_object>? That would make their implementation trivial, but it would require the user to construct a potentially useless, dynamically allocated object just to call a function.
sell_object const&? That seems correct, but there are two problems with it: 1) we would still need to construct an std::unique_ptr with a copy of passed argument to find the appropriate element to remove; 2) we wouldn't be able to correctly implement add(), since we need the concrete type to construct an actual std::unique_ptr to put into our map.
Let us go with the second option and fix the first problem. We certainly do not want to construct a useless and expensive object just to look for it in the storage map. Ideally we would like to find a key (std::unique_ptr<sell_object>) that matches the passed object. Fortunately, transparent hashers and comparators come to the rescue.
By supplying additional overloads for hasher and comparator (and providing a public is_transparent alias), we allow for looking for a key that is equivalent, without needing the types to match:
struct sell_object_hash {
auto operator()(std::unique_ptr<sell_object> const& object) const -> std::size_t {
return object->hash();
}
auto operator()(sell_object const& object) const -> std::size_t {
return object.hash();
}
using is_transparent = void;
};
struct sell_object_equal {
auto operator()(
std::unique_ptr<sell_object> const& lhs,
std::unique_ptr<sell_object> const& rhs
) const -> bool {
return (*lhs <=> *rhs) == 0;
}
auto operator()(
sell_object const& lhs,
std::unique_ptr<sell_object> const& rhs
) const -> bool {
return (lhs <=> *rhs) == 0;
}
auto operator()(
std::unique_ptr<sell_object> const& lhs,
sell_object const& rhs
) const -> bool {
return (*lhs <=> rhs) == 0;
}
using is_transparent = void;
};
Thanks to that, we can now implement shop::remove() like so:
auto remove(sell_object const& to_remove) -> void {
if (auto it = storage.find(to_remove); it != storage.end()) {
it->second--;
if (it->second == 0) {
storage.erase(it);
}
}
}
Since our comparator and hasher are transparent, we can find() an element that is equivalent to the argument. If we find it, we decrement the corresponding count. If it reaches 0, we remove the entry completely.
Great, onto the second problem. Let us list the requirements for the shop::add():
we need the concrete type of the object (merely a reference to the base class is not enough, since we need to create matching std::unique_ptr).
we need that type to be derived from sell_object.
We can achieve both with a constrained* template:
template <std::derived_from<sell_object> T>
auto add(T const& to_add) -> void {
if (auto it = storage.find(to_add); it != storage.end()) {
it->second++;
} else {
storage[std::make_unique<T>(to_add)] = 1;
}
}
This is, again, quite simple
*References: {1} {2}
Correct destruction semantics
There is only one more thing that separates us from the correct implementation. It's the fact that if we have a pointer (either smart or not) to a base class that is used to deallocate it, the destructor needs to be virtual.
This leads us to the final version of the sell_object class:
struct sell_object {
virtual auto hash() const -> std::size_t = 0;
virtual auto operator<=>(sell_object const&) const -> std::partial_ordering = 0;
virtual ~sell_object() = default;
};
See full implementation with example and additional printing utilities.
Can somebody explain or help me why this isnt working?
std::vector<std::shared_ptr<Publication> > Bibliography::givePubWithIFHigherThan(float value) const
{
Publication *p;
std::vector<std::shared_ptr<Publication>> highIFPubs(publications);
auto checkIF = std::mem_fun(p->IFHigherThan(value));
auto last = std::copy_if(publications.begin(), publications.end, highIFPubs.begin(),
[=] (std::shared_ptr<Publication> p)
{
return checkIF(*p, value);
});
return highIFPubs;
}
class Publication
{
public:
Publication(std::string aTitle, int aYear, std::string anID);
virtual bool IFHigherThan(float value) const {return false;};
private:
};
class Paper : public Publication
{
public:
Paper(std::string aTitle, int aYear, std::string aJournal, float aImpactFactor);
bool IFHigherThan(float value) const {return value < impactFactor;};
private:
};
At the moment i get this error,
no matching function for call to 'mem_fun(bool)'
auto checkIF = std::mem_fun(p->IFHigherThan(value));
^
std::mem_fun is a depracated helper function that will probably be soon removed from the standard library. std::mem_fn would be a better choice.
Moreover, if you want to use std::mem_fn, std::mem_fun or std::bind with a function, then you pass in a pointer to function, not a call expression, so instead of:
auto checkIF = std::mem_fun(p->IFHigherThan(value));
use:
auto checkIF = std::mem_fn(&Publication::IFHigherThan);
Alternatively, don't use any wrapper, just directly call the selected member function:
auto last = std::copy_if(publications.begin(), publications.end(), highIFPubs.begin(),
[=] (std::shared_ptr<Publication> p)
{
return p->IFHigherThan(value);
});
There is one more logical error you have in your code:
std::vector<std::shared_ptr<Publication>> highIFPubs(publications.size());
should be:
std::vector<std::shared_ptr<Publication>> highIFPubs;
and then instead of:
auto last = std::copy_if(publications.begin(), publications.end()
, highIFPubs.begin(),
// ~~~~~~~~~~~~~~~~~^
you should be using std::back_inserter:
auto last = std::copy_if(publications.begin(), publications.end()
, std::back_inserter(highIFPubs),
// ~~~~~~~~~~~~~~~~~^
as you don't actually know how many elements will the resultant vector have.
Suppose you have different predicates (function objects with initial state in this particular case) that you use with STL algorithms (copy_if ,sort etc...). Thing is that predicates can change at runtime by configuration change or user input. I've thought using polymorphism and virtual operator(), then settled on a std::function solution like this (this puts me in C++11 realm but that's okay)
struct PlainFilter {
PlainFilter(string filter):m_filter(filter)
{}
bool operator() (const string& toMatch)
{one way}
};
struct AcronymFilter {
AcronymFilter (string filter):m_filter(filter)
{}
bool operator() (const string& toMatch)
{a different way}
};
enum FilterTypes {plain,acronym};
vector<string> FilterStuff(string filter, vector<string> in)
{
vector<string> out;
std::function<bool(const string&)> foo;
if( filterType == plain)
foo = PlainFilter(filter);
else if( filterType == acronym)
foo = AcronymFilter(filter);
copy_if(in.begin(),in.end(),back_inserter(out),foo);
return out;
}
Is this good?
I'd rather avoid that if else statements everytime I need to filter strings since filter type might change once or none at all throughout the lifetime of the program.
Any other different take on the problem is also welcome..
There are probably several ways to do this, but this is what polymorphism is for. Your code is simplified, and you don't have to remember to add a new case or else if for any new filters you dream up in every place it could be used.
struct IFilter
{
virtual bool operator()(const std::string &) const = 0;
};
struct PlainFilter : public IFilter
{
virtual bool operator()(const std::string &filter) const override
{
// do something
}
};
struct AcronymFilter : public IFilter
{
virtual bool operator()(const std::string &filter) const override
{
// do something else
}
};
std::vector<std::string> FilterStuff(const IFilter &filter, const std::vector<std::string> &in)
{
std::vector<std::string> out;
std::copy_if(in.begin(), in.end(), std::back_inserter(out), filter);
return out;
}
However, I personally would implement FilterStuff differently. You're taking strings from one vector and copying them to another, and then presumably another piece of code is going to iterate over that new vector and do something with those filtered strings. Consider instead a design that takes a filter and the "do something with it" function:
void EnumerateStuff(const IFilter &filter, const std::vector<std::string> &in,
std::function<void(std::string)> callback)
{
for (const auto &s : in)
{
if (filter(s))
{
callback(s);
}
}
}
FilterStuff can now be written in terms of EnumerateStuff, if it's really necessary to have a filtered copy:
std::vector<std::string> FilterStuff(const IFilter &filter, const std::vector<std::string> &in)
{
std::vector<std::string> out;
EnumerateStuff(filter, in,
[&](const std::string &s)
{
out.push_back(s);
});
return out;
}
what is a filterType variable in your example? I guess some configurable parameter for your application/algorithm??
anyway, I'd propose the following:
1) collect all configurable parameters into a structure:
class configuration
{
public:
/// Type of predicate functor
typedef std::function<bool(const std::string&)> predicate_type;
struct plain_filter { /* your implementation */ };
struct acronym_filter { /* your implementation */ };
/// Type of predicate to use
enum class predicate_type { plain, acronym };
/// Set predicate
void set_filter_kind(const predicate_type ft)
{
switch (ft)
{
case predicate_type::plain:
m_predicate = plain_filter();
break;
case predicate_type::acronym:
m_predicate = acronym_filter();
break;
default:
assert(!"Invalid filter type");
}
}
/// Get filter to be used by algorithms
/// \todo Consider to return a const reference instead of copy,
/// but make sure your filters are state-less (as expected by STL slgorithms)
decltype(m_predicate) use_filter() const
{
return m_predicate;
}
// other configuration parameters/methods
private:
predicate_type m_predicate;
};
2) fill an instance of configuration from command-line options, config file, or user input. Make this instance visible to all required parts of your code (for example make it the member of your application class and provide a method to (read-only) access it or smth like this...)
3) use configuration data in your algorithms
std::vector<string> filter_stuff(string filter, const std::vector<string>& in)
{
std::vector<string> out;
std::copy_if(
begin(in)
, end(in)
, std::back_inserter(out)
, application().get_config().use_filter()
);
return out;
}
PS: btw, pass in parameter via reference (or rvalue reference)... I'm really in doubt that you need a copy (pass it by value)
Since filterType is a runtime value, you're going to have some kind of selection going on here. There's your if's, a switch, or an array lookup. I like the switch best, the array lookup requires more complex types and isn't any faster once the optimizer gets through with it. Also I'd pass the initializers by const & not by value just on reflex.
That said, I think your method's fine. Another way to do it:
bool plain_string_test(const string& filter, const string& candidate)
{ /* ... one way ... */ }
bool acronym_string_test(const string& filter, const string& candidate)
{ /* ... or another ... */ }
enum FilterTypes {plain,acronym};
vector<string> FilterStuff(string filter, vector<string> in)
{
vector<string> out;
std::function<bool(const string&, const string&)> filtermethod;
switch(filterType) {
default: throw some_domain_error(some_constructor_here);
case plain: filtermethod = plain_string_test; break;
case acronym: filtermethod = acronym_string_test; break;
}
copy_if(in.begin(),in.end(),back_inserter(out),
[&filtermethod, &filter](const string& candidate) -> bool
{ return filtermethod(filter,candidate); }
return out;
}
I do like this one better; it eliminates some type scaffolding and some copying and makes the string tests more reusable (or also elminates some function scaffolding). Here you might be able to get better value from a static array of functions, it'd probably depend on context.
keyboard-to-editbox warning, I haven't tested this code but I do believe it's at least correct enough for communication.
So I want to create a simple map std::map<T1, std::string> and I have a function that returns std::string I want somehow to link item creation in std::map with my function so that when my_map[some_new_element] is called my function will be called and its return set to value for some_new_element key. Is such thing possible and how to do it?
You can wrap the map itself or the value type or operator[].
Last wrapper will be the simplest:
template <typename T>
std::string& get_default(std::map<T, std::string>& map, const T& key) {
auto it = map.find(key);
if (it == map.end()) {
return map[key] = create_default_value();
} else {
return *it;
}
}
The value type shouldn't be too hard, either:
struct default_string {
std::string wrapped_string;
default_string() : wrapped_string(create_default_value()) {}
explicit default_string(const std::string& wrapped_string)
: wrapped_string(wrapped_string) {}
operator const std::string&() const { return wrapped_string; }
operator std::string&() { return wrapped_string; }
};
Wrapping map will take a bit more work, as you'd have to duplicate the entire interface, including typedefs. Note: this code is not tested, treat it as proof-of-concept, to steer you in the right direction.
What about a small wrapper class for std::string?
class StringWrapper {
StringWrapper() { //... your code
}
operator std::string&() { return m_string; } // or something like that
private:
std::string m_string;
};
Now you use the following map-type:
std::map<T1, StringWrapper> mymap;
In the constructor of StringWrapper you can define custom actions. It gets called when you insert an item into your map.