I'm trying to create an Enum that has a string label and a value and I plan to use this to read stuff from an ini file.
For example in the ini file I might have some double, int or string type values preceded by the tag/name of the value:
SomeFloat = 0.5
SomeInteger = 5
FileName = ../Data/xor.csv
When I read the tag from a file it comes in as a string, so I'd just like to have std::set that keeps all of my values... when I read the tag I can just compare it against the EnumType and if matches the label then I will check the type and do the proper conversion (atoi or just use the string, etc.)
For example:
EnumType<int> someInteger;
someInteger.label = "SomeInteger";
someInteger.type = INT;
std::set<EnumType> myValues;
//
// populate the set
myValues.insert(someInteger);
//
void ProcessTagAndValue(const std::string &tag, const std::string &value)
{
switch(myValues[tag].type)
{
case INT:
myValues[tag].value = atoi(value);
break;
case DOUBLE:
//
break;
case STRING:
myValues[tag].value = value;
break;
default:
break;
}
}
enum ValueType{INT,DOUBLE,STRING];
template <class T>
struct EnumType{
std::string label;
ValueType type;
T value;
bool operator==(const EnumType &other) const {
return this->label == other.label;
}
bool operator==(const T& other ) const
{
return this->value == other;
}
T& operator=(const T& p)
{
value = p;
return value;
}
EnumType& operator=(const EnumType& p)
{
if (this != &p) { // make sure not same object
this->label = p.label;
this->value = p.value;
}
return *this;
}
};
I have several questions:
Can you guys tell me any better solutions? I'm not sure if I'm trying to be too clever for my own good, or if this is really a viable solution.
If my solution is acceptable, then can anybody tell me how I can declare a set of std::set<EnumType<...>> so that it can accept any type (int, double, string) without me actually knowing which type the enum is going to be using for the value?
If you have any code, then it would be GREAT! :)
If you have limited and very stable set of types, then Boost.Variant may be used.
If you going to add support for new types later, then better forget about this method. In this situation solution, based on Boost.Any, or pair of strings will be better.
typedef boost::variant<int, double, std::string> ValueType;
struct EnumType {
std::string label;
ValueType value;
};
Another question is: "How these values will be used later?" If you are going to pass "SomeInteger" to function, accepting int, you still have to run code similar to:
acceptInt( get<int>( v.value ) ); // get may throw
This approach works better when you have uniform processing of fixed set of types:
class processValue : public boost::static_visitor<>
{
public:
void operator()(int i) const
{
acceptInt( i );
}
void operator()(double d) const
{
acceptDouble( d );
}
void operator()(const std::string & str) const
{
acceptString( str );
}
};
boost::apply_visitor( processValue(), v.value );
Have you looked at Boost.Any? It should do what you want (and if you need to roll your own, you can peek at the source for hints).
Related
I have made a class that holds all the data that is read from a file.
class Entry
{
std::string key;
std::string value;
std::vector<std::string> arrayString;
std::vector<Entry> arrayEntry;
enum dataValueCheck
{
str,arrStr,arrEnt
};
dataValueCheck dataValue;
public:
Entry(std::string key, std::string value)
:key(key), value(value)
{
dataValue = str;
}
Entry(std::string key, std::vector<std::string> value)
:key(key), arrayString(value)
{
dataValue = arrStr;
}
Entry(std::string key, std::vector<Entry> value)
:key(key), arrayEntry(value)
{
dataValue = arrEnt;
}
std::string getKey()
{
return key;
}
template <typename T>
T getData()
{
switch (dataValue)
{
case Entry::str:
return value;
case Entry::arrStr:
return arrayString;
case Entry::arrayEntry:
return arrayEntry;
default:
break;
}
}
};
This file is basically a JSON file and resembles this structure.
{
Name:Test,
Arr_Item:[1,2,3],
Sub_Item:
{
Item1 : [0,1,2,3],
Item2 : [4,5,6],
}
}
I have it reading the data in just fine. Where I am getting stuck is retrieving the data using the getData function that is part of the class. When I try and use a template I get an "illegal case" error. How can I get the data out of this class?
UPDATE
I have a vector filled with the Entry classes. When I try and access it like this:
std::vector<Entry> DB;
std::string test = DB[0].getData<std::string>();
It should return "Test", but instead I get this error:
1>C:\Dev\Playground\Playground\src\Main.cpp(454,1): error C2440: 'return': cannot convert from 'std::vector<Entry,std::allocator<Entry>>' to 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
1>C:\Dev\Playground\Playground\src\Main.cpp(454,4): message : No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
This all gets very easy if you drop the template and return a std::variant from getData. It gets easier still if you store the value in Entry as a std::variant in the first place (you only need to store one of the types in any particular instance of Entry, right?).
Here's a fully worked-up example. Note that I pass the parameters to the constructor by const reference to avoid unnecessary copies; you might also return (const) references from getKey and getData for the same reason:
#include <iostream>
#include <variant>
#include <string>
#include <vector>
class Entry
{
using EntryType = std::variant <std::string, std::vector <std::string>, std::vector <Entry>>;
std::string key;
EntryType value;
public:
template <typename T>
Entry(const std::string &key, const T &value) : key(key), value(value) {}
std::string getKey() { return key; }
EntryType getData() { return value; }
};
int main() {
Entry e { "key", "string value" };
std::cout << std::get <std::string> (e.getData ());
}
Your getData function did not handle for the default case in switch statement. You can either return something such as nullptr or throw an exception in case
template <typename T = nullptr_t>
T getData()
{
switch (dataValue)
{
// Cases
default:
return nullptr;
}
}
The issue is that not all return values can be validly converted to T for any particular T.
Let's take the example from your posted example. We have this expression:
DB[0].getData<std::string>();
If we subsitute std::string for T, this is what getData() looks like:
std::string getData()
{
switch (dataValue)
{
case Entry::str:
return value;
case Entry::arrStr:
return arrayString;
case Entry::arrayEntry:
return arrayEntry;
default:
break;
}
}
Only the Entry::str case is valid here as it actually returns a std::string. The other cases return either a std::vector<std::string> or a std::vector<Entry>, neither of which can be converted to a std::string.
There are many ways to resolve this, but probably the best is to recognize that Entry is basically a key mapped to a std::variant. If getData() returned a std::variant, you would avoid many potential pitfalls while relying on a standard type. Alternatively, if you want to stick with your current approach, you would need some way of returning the right value based on T, not (only) based on dataValue. I.e., you need some way of handling the possibility that T does not match the expectation set by dataValue.
As an alternative to returning a variant, you could use if constexpr in getData
template <typename T>
T getData()
{
if constexpr(std::is_same_v<T, std::string>) {
return (dataValue == Entry::str) ? value : throw type_error(dataValue, Entry::str);
} else if constexpr(std::is_same_v<T, std::vector<std::string>>) {
return (dataValue == Entry::arrStr) ? arrayString : throw type_error(dataValue, Entry::arrStr);
} else if constexpr(std::is_same_v<T, std::vector<Entry>>) {
return (dataValue == Entry::arrEnt) ? arrayEntry : throw type_error(dataValue, Entry::arrEnt);
} else {
#error Invalid type requested for Entry::getData
}
}
I know the title is not meaningful, couldn't find anything better.
I need to provide a C++ interface to an SQlite table, where I can store key/value/type configuration settings, such as
Key | Value | Type
PATH | /path/to/ | STRING
HAS_FEATURE | Y | BOOLEAN
REFRESH_RATE| 60 | INTEGER
For simplicity and flexibility purposes the datamodel hosts the values as strings but provides a column to retain the original data type.
This is how I have imagined a client to call such c++ interface.
Configuration c;
int refreshRate = c.get<int>("REFRESH_RATE");
// Next line throws since type won't match
std::string refreshRate = c.get<std::string>("REFRESH_RATE");
This is how I have imagined implementing it (I know the code won't compile as is, consider it as pseudo c++, I'm more questioning the design than the syntax here)
class Parameter
{
public:
enum KnownTypes
{
STRING = 0,
BOOLEAN,
INTEGER,
DOUBLE,
...
}
std::string key;
std::string value;
KnownTypes type;
}
class Configuration
{
public:
template<class RETURNTYPE>
RETURNTYPE get(std::string& key)
{
// get parameter(eg. get cached value or from db...)
const Parameter& parameter = retrieveFromDbOrCache(key);
return <parameter.type, RETURNTYPE>getImpl(parameter);
}
private:
template<int ENUMTYPE, class RETURNTYPE>
RETURNTYPE getImpl(const Parameter& parameter)
{
throw "Tthe requested return type does not match with the actual parameter's type"; // shall never happen
}
template<Parameter::KnownTypes::STRING, std::string>
std::string getImpl(const Parameter& parameter)
{
return parameter.value;
}
template<Parameter::KnownTypes::BOOLEAN, bool>
std::string getImpl(const Parameter& parameter)
{
return parameter.value == "Y";
}
template<Parameter::KnownTypes::INTEGER, int>
int getImpl(const Parameter& parameter)
{
return lexical_cast<int>(parameter.value)
}
// and so on, specialize once per known type
};
Is that a good implementation ? Any suggestions on how to improve it ?
I know I could have specialized the public get directly per return type, but I would have duplicated some code in each template specialization (the type consistency check as well as the parameter retrieval)
Your approach will fail badly if you try to implement it out! Problem is:
return <parameter.type, RETURNTYPE>getImpl(parameter);
or with correct C++ syntax:
return getImpl<parameter.type, RETURNTYPE>(parameter);
Template parameters require to be compile time constants, which parameter.type is not! So you would have to try something like this:
switch(parameter.type)
{
case STRING:
return getImpl<STRING, RETURNTYPE>(parameter);
//...
}
Does not look like you gained anything at all, does it?
You might try the other way round, though, specialising the getter itself:
public:
template<class RETURNTYPE>
RETURNTYPE get(std::string const& key);
template<>
std::string get<std::string>(std::string const& key)
{
return getImpl<STRING>(key);
}
template<>
int get<int>(std::string const& key)
{
return lexical_cast<int>(getImpl<STRING>(key));
}
private:
template<KnownTypes Type>
std::string getImpl(std::string const& key)
{
Parameter parameter = ...;
if(parameter.type != Type)
throw ...;
return parameter.value;
}
Or without templates (referring Nim's comment...):
public:
int getInt(std::string const& key)
{
return lexical_cast<int>(getImpl(STRING, key));
}
private:
inline std::string getImpl(KnownTypes type, std::string const& key)
{
Parameter parameter = ...;
if(parameter.type != type)
throw ...;
return parameter.value;
}
One change you might have noticed: I fixed constness for your parameters...
Side note: template specialisations as above are not allowed at class scope (above is written for shortness). In your true code, you have to move the specialisations out of the class:
struct S { template<typename T> void f(T t); };
template<> void S::f<int>(int t) { }
In addition to the accepted answer I would like to add a demo with a little difference of verifying the correctness of the type without boilerplatting the code over all template specializations. As well correcting the explicit-template specialization in the class scope that is not allowed.
class Parameter {
public:
enum KnownTypes { STRING = 0, BOOLEAN, INTEGER, DOUBLE };
std::string key;
std::string value;
KnownTypes type;
};
class Configuration {
public:
template <class RETURNTYPE>
RETURNTYPE get(std::string const& key) {
// get parameter(eg. get cached value or from db...)
std::map<std::string, Parameter> map{
{"int", Parameter{"int", "100", Parameter::KnownTypes::INTEGER}},
{"string", Parameter{"string", "string_value", Parameter::KnownTypes::STRING}},
{"throwMe", Parameter{"throwMe", "throw", Parameter::KnownTypes::DOUBLE}},
{"bool", Parameter{"bool", "Y", Parameter::KnownTypes::BOOLEAN}}};
const Parameter& parameter = map.at(key);
bool isMatchingType = false;
switch (parameter.type) {
case Parameter::STRING:
isMatchingType = std::is_same<RETURNTYPE, std::string>::value;
break;
case Parameter::BOOLEAN:
isMatchingType = std::is_same<RETURNTYPE, bool>::value;
break;
case Parameter::INTEGER:
isMatchingType = std::is_same<RETURNTYPE, int>::value;
break;
case Parameter::DOUBLE:
isMatchingType = std::is_same<RETURNTYPE, double>::value;
break;
};
if (!isMatchingType)
throw "Tthe requested return type does not match with the actual parameter's type";
return getImpl<RETURNTYPE>(parameter);
}
private:
template <class RETURNTYPE>
RETURNTYPE getImpl(const Parameter& parameter);
};
template <>
std::string Configuration::getImpl<std::string>(const Parameter& parameter) {
return parameter.value;
}
template <>
bool Configuration::getImpl<bool>(const Parameter& parameter) {
return parameter.value == "Y";
}
template <>
int Configuration::getImpl<int>(const Parameter& parameter) {
return std::stoi(parameter.value);
}
int main() {
Configuration conf;
cerr << conf.get<int>("int") << endl;
cerr << conf.get<bool>("bool") << endl;
cerr << conf.get<string>("string") << endl;
cerr << conf.get<string>("throwMe") << endl;
return 0;
}
I'm trying to make a list of template classes of variable types. So the idea is to loop of a list of objects that all have a common function, e.g. getValue, but a different type. The type could be any type, raw types or objects.
I need this because i want to have a class that has a list of attributes of different types that i want to be able to construct at runtime.
So my class would look something like:
class MyClass {
std::list<Attribute<?>*> attributes;
};
And my attribute template:
template<typename T>
class Attribute {
public:
Test(const T &t) : _t(t) {}
T getValue() const { return _t; }
void setValue(const T &t) { _t = t; }
private:
T _t;
};
int main() {
MyClass myClass;
myClass.attributes.push_back(new Attribute<int>(42));
myClass.attributes.push_back(new Attribute<double>(42.0));
}
As you can see the list of MyClass i put ? because that is my problem. I dont know how to make a list that will take different types of my Attribute template, i.e. int, double etc.
std::list<Attribute<?> *> attributes;
In Java, generics can be used for that. Is it possible in C++ to do this with somekind of construction? I tried using variadic templates but that doesnt seem to help solving my problem.
I need this but not in Java, in C++:
public class GenericAttribute<T> {
private T value;
public GenericAttribute (T value) {
setValue(value);
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
public static void main(String[] args) {
class Custom {
public Custom() {}
#Override public String toString() {
return "My custom object";
}
}
List<GenericAttribute<?>> attributes = new ArrayList<GenericAttribute<?>>();
attributes.add(new GenericAttribute<Integer>(1));
attributes.add(new GenericAttribute<Double>(3.1415926535));
attributes.add(new GenericAttribute<Custom>(new Custom()));
for (GenericAttribute<?> attr : attributes) {
System.out.println(attr.getValue());
}
}
Output:
1
3.1415926535
My custom object
Thanks for the help!
Version 3: Very Advanced (do not try that at home :D)
class Attribute {
private:
struct Head {
virtual ~Head() {}
virtual void *copy() = 0;
const type_info& type;
Head(const type_info& type): type(type) {}
void *data() { return this + 1; }
};
template <class T> struct THead: public Head {
THead(): Head(typeid(T)) {}
virtual ~THead() override { ((T*)data())->~T(); }
virtual void *copy() override {
return new(new(malloc(sizeof(Head) + sizeof(T)))
THead() + 1) T(*(const T*)data()); }
};
void *data;
Head *head() const { return (Head*)data - 1; }
void *copy() const { return data ? head()->copy() : nullptr; }
public:
Attribute(): data(nullptr) {}
Attribute(const Attribute& src): data(src.copy()) {}
Attribute(Attribute&& src): data(src.data) { src.data = nullptr; }
template <class T> Attribute(const T& src): data(
new(new(malloc(sizeof(Head) + sizeof(T))) THead<T>() + 1) T(src)) {}
~Attribute() {
if(!data) return;
Head* head = this->head();
head->~Head(); free(head); }
bool empty() const {
return data == nullptr; }
const type_info& type() const {
assert(data);
return ((Head*)data - 1)->type; }
template <class T>
T& value() {
if (!data || type() != typeid(T))
throw bad_cast();
return *(T*)data; }
template <class T>
const T& value() const {
if (!data || type() != typeid(T))
throw bad_cast();
return *(T*)data; }
template <class T>
void setValue(const T& it) {
if(!data)
data = new(new(malloc(sizeof(Head) + sizeof(T)))
THead<T>() + 1) T(it);
else {
if (type() != typeid(T)) throw bad_cast();
*(T*)data = it; }}
public:
static void test_me() {
vector<Attribute> list;
list.push_back(Attribute(1));
list.push_back(3.14);
list.push_back(string("hello world"));
list[1].value<double>() = 3.141592;
list.push_back(Attribute());
list[3].setValue(1.23f);
for (auto& a : list) {
cout << "type = " << a.type().name()
<< " value = ";
if(a.type() == typeid(int)) cout << a.value<int>();
else if (a.type() == typeid(double)) cout << a.value<double>();
else if (a.type() == typeid(string)) cout << a.value<string>();
else if (a.type() == typeid(float)) cout << a.value<float>();
cout << endl;
}
}
};
Output:
type = i value = 1
type = d value = 3.14159
type = Ss value = hello world
type = f value = 1.23
Explanation:
Attribute contains data pointer, which is initializaed by this strange placement new: new(new(malloc(sizeof(Head) + sizeof(T))) THead<T>() + 1) T(src) which first allocates enough room for the Head (should be 2*sizeof(void*) which should be just fine for any allignment of any architecture) and the type itself, constructs THead<T>() (initializes pointer to virtual method table and type info) and moves the pointer after the head = at the place we want data. The object is then constructed by another placement new using copy-constructor (or move-constructor) T(src). struct Head has two virtual functions - destructor and copy() which is implemented in THead<T> and used in Attribute(const Attribute&) copy-constructor. Finally ~Attribute() destructor calls ~Head() virtual destructor and releases the memory (if data != nullptr).
Version 1: Simple Attribute List
#include <vector>
#include <typeinfo>
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;
class Attributes {
public:
typedef pair<const type_info&,void*> value_type;
typedef vector<value_type> vect;
typedef vect::const_iterator const_iterator;
template <class T>
void add(const T& value) {
data.push_back(pair<const type_info&,void*>(
typeid(T), new(malloc(sizeof(T))) T(value))); }
const_iterator begin() const {
return data.begin(); }
const_iterator end() const {
return data.end(); }
private:
vect data;
} attrs;
int main() {
attrs.add(1);
attrs.add(3.14);
for (auto a : attrs) {
cout << a.first.name() << " = ";
if(a.first == typeid(int))
cout << *(int*)a.second;
else if(a.first == typeid(double))
cout << *(double*)a.second;
cout << endl;
}
}
Output:
i = 1
d = 3.14
Version 2 (named attributes):
#include <string>
#include <unordered_map>
#include <typeinfo>
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;
class Attributes {
public:
typedef pair<const type_info&,void*> value_type;
typedef unordered_map<string,value_type> map;
typedef map::const_iterator const_iterator;
template <class T>
bool add(const string& name, const T& value) {
auto it = data.insert(make_pair(
name, value_type(typeid(T), nullptr)));
if (!it.second) return false;
it.first->second.second = new(malloc(sizeof(T))) T(value);
return true; }
template <class T>
const T& get(const string& name) const {
auto it = data.at(name);
if (it.first != typeid(T)) throw bad_cast();
return *(T*)it.second; }
const_iterator begin() const {
return data.begin(); }
const_iterator end() const {
return data.end(); }
void erase(const_iterator it) {
free(it->second.second);
data.erase(it); }
bool remove(const string& name) {
auto it = data.find(name);
if (it == data.end()) return false;
free(it->second.second);
data.erase(it);
return true; }
private:
map data;
} attrs;
int main() {
attrs.add("one", 1);
attrs.add("pi", 3.14);
cout << "pi = " << attrs.get<double>("pi") << endl;
attrs.remove("pi");
for (auto a : attrs) {
cout << a.first << " = ";
if(a.second.first == typeid(int))
cout << *(int*)a.second.second;
else if(a.second.first == typeid(double))
cout << *(double*)a.second.second;
cout << endl;
}
}
Take a look at variant - this is a class that can be one of a number of different types, but you don't mind which until you need to operate on the values, in which case you can use the visitor pattern to visit all the types.
It is effectively a C++ type-aware version of the C 'union' construct but as it 'knows' which type was set, it can offer type safety.
The biggest issue with variants is that if you expose your implementation and allow any client to put pretty much any type into your variant (attributes.push_back(new Attribute<Widget>(myWidget));), you're going to be unable to do anything with it. E.g. if you want to do 'sum' on all the values put into your attributes, you'd need them to be convertible to a numeric representation and a Widget might not be.
The bigger question is what are you trying to do with them once you've captured these items as Attributes? Enumerating through them calling getValue() is going to give you different results depending on what types you put in. A visitor object would work, but it's still not clear what value this would bring.
It could be that you need something different, such as an interface, e.g. IAttribute that abstracts the underlying type as long as it conforms to the interface which has a getValueAsDouble() method or getValueAsString() method, which you could do to any type that got passes in - no need for variant or visitor in this case.
As Attribute<int> is different type than Attribute<double>, you can't use list or vector without creating a common base type.
Alternatively, you may store different type into a std::tuple.
Following may help:
template <typename ... Ts>
class MyClass {
public:
MyClass(const Ts&... args) : attributes(args...) {}
private:
std::tuple<Attribute<Ts>...> attributes;
};
int main()
{
MyClass<int, double> myClass(42, 42.0);
return 0;
}
As you already pointed out, in java it is much easier to do so because all classes extends java.lang.Object. In C(++), there is a similar way, but only for pointers - you can use void* for this. Your list will look something like this then:
std::list<Attribute<void*> *> attributes;
Sure, a void* doesn't save the type. If you need, add this field to your Attribute-class:
public:
std::type_info type;
Then, if you create an instance of Attributre, do this (probably from the constructor):
type = typeinfo(type_to_store);
Of corse, if you do so from the constructor, you'll need to run typeinfo in the code that calls the constructor.
Then, you can get the name of the class back from that field and the instance back from your void*:
std::string name = attribute->type_info.name();
void * instance = attribute->getValue();
What operations do you want to perform on this collection? Do you want to, say, call getValue on all of the Attribute<int> instances and ignore the others? In that case, a base class is fine—you just don’t make the getValue member function virtual (because its type depends on the subclass) and use RTTI to recover the type information at runtime:
struct AnyAttribute {
// Needs at least one virtual function to support dynamic_cast.
virtual ~AnyAttribute() {}
};
template<typename T>
struct Attribute : AnyAttribute { … };
int main() {
std::vector<AnyAttribute*> attributes;
attributes.push_back(new Attribute<int>(13));
attributes.push_back(new Attribute<int>(42));
attributes.push_back(new Attribute<double>(2.5));
for (const auto attribute : attributes) {
if (const auto int_attribute = dynamic_cast<Attribute<int>>(attribute)) {
std::cout << int_attribute->getValue() << '\n';
}
}
}
An obvious, but naive solution will be:
inherit Attribute<T> from base AttributeBase
store AttributeBase (smart-)pointers in container (downcast)
when reading container element, somehow figure out its type (RTTI)
cast back to derived Attribute<T> (upcast)
You can beautify this ugly solution by adding another level of indirection: make a generic container, that stores generic containers for each attribute, so casting will happen under the hood.
You can use integrated to language RTTI features, such as type_info but as far as I know, it's reliability is questionable. Better solution will be to wrap up some kind of static unique id to each Attribute<T> class and put an accessor to AttributeBase to retrieve it. You can add a typedef to relevant Attribute<T> to your unique id class to make casting easier.
So far so good, but in modern C++, we know, that when you need RTTI, it, probably, means that there is something wrong with your overall code design.
I don't know what exact task you have, but my "gut feelings" say me that you will probably can eliminate need of RTTI by using multiple dispatch (double dispatch), such as Visitor pattern in your code (they always say that when see RTTI).
Also, check some other tricks for inspiration:
More C++ Idioms/Coercion by Member Template
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.