Multifunctional enumerator, std::any{C++17} and templates.
Multifunctional enumerator, std::any{C++17} and templates.
In many cases, you will wonder to enumerate all possible candidates in the dictionary, by the way, it’s using in Brute-force search. Also, enumerations can be using in permutation of objects. But first things first.
Let’s imagine you have a locked door, and the door will be opened if you give correct user information (age, person height, and secret letter).
Problem: you forget your height and your secret letter, but you know approximately your height [180 - 190] centimeter. You have 3 options to unlock this door:
- try better to remember all info
- restore access
- enumerate all possible candidate in the dictionary.
In our case we have the only 3rd option, we need to take first possible height value and enumerate all possible secret keys, take second one possible key and keep going on.
Height cm. | Secret letter |
---|---|
180.0 | A |
180.0 | B |
180.0 | … |
180.0 | Z |
180.1 | A |
180.1 | B |
…. | … |
190.0 | Z |
Enumaration bounds:
- Secret letter: ‘A’,’B’, …‘Z’
- Height sm. : 180.0, 180.1, 180.2, 180.3, … 190
We can compute total number of enumerations, it will be :
(total possible heights) * (total possible letters)
((190 - 180) * 10 + 1) * (Total length of english alphabet)
101 * 26
2626
Let’s try to design interface for our future enumerator:
Enumerations::Enumerator()
.addEnumeration("secret letter", Enumerations::Enumeration<char>('A', 'Z', 1)) //Lower bound 'A', upper bound 'Z', step 1
.addEnumeration("height", Enumerations::Enumeration<double>(180.0, 190.0, 0.1 )) //LB 180, UB 190, step 0.1
//Pass lambda function into enumeration function
.enumerate([](Enumerations::EnumerationMap enumerationMap) {
//Use current enumeration
std::cout << enumerationMap.get<char>("secret letter") << ' '
<< enumerationMap.get<double>("height") << std::endl;
});
- class
Enumeration
will play the role of the store for upper bound, lower bound, step and the current value of the enumeration. For enumeration through height in our caseEnumeration
will have 180.0 - as lower bound, 190.0 - upper bound, 0.1 step and current value will be lower bound, but in next state current value will be lower bound + step. - class
Enumerator
will hold allEnumeration
class instances, and has functionenumerate
.enumerate
will receive a pointer to function or lambda as an input argument, and call them when new enumeration will be generated. This lambda should receive as input argument class instanceEnumerationMap
which hold value for the current enumeration.
- class
EnumerationMap
hold values for current enumeration,also they has function for insert value bystd::string
as key andany
as value, function for get value bystd::string
key which will be converted to type which will be passed into function as template input argument.
Enumeration
class
As we can see we need a container with different data types, Enumerations::Enumeration<TYPE>
.
We can store base class inside a container and insert derived template class into a container.
class EnumerationBase {
public:
virtual ~EnumerationBase() {}
EnumerationBase(const EnumerationBase &pb) = default;
EnumerationBase() = default;
virtual std::experimental::fundamentals_v1::any current() = 0;
virtual void next() = 0; // Go to next value
virtual void reset() = 0; // Reset current value to start value
virtual bool hasNext() = 0; // Check we have next value
};
template<class T>
class Enumeration : public EnumerationBase {
public:
Enumeration(const Enumeration &pb) = default;
Enumeration() = default;
virtual ~Enumeration() = default;
virtual std::experimental::fundamentals_v1::any current() override {
return current_val;
}
virtual void next() override {
if (!this->hasNext()) {
throw std::out_of_range("Current enumeration doesn't has next values");
}
current_val += step;
}
virtual void reset() override {
this->current_val = this->start;
}
virtual bool hasNext() override {
return current_val + step <= end;
}
Enumeration(T start, T end, T step) : start(start), current_val(start), end(end), step(step) {}
private:
T start;
T end;
T step;
T current_val;
};
We will store EnumerationBase
in our container, but we will add derived Enumeration<Type>
to our container, where Type
is object with implemented +=
,+
,<=
,=
operators, like base types(int
, char
, double
, etc).
As you can see we added default keyword after function definition. In few words, default tells to the compiler generate this function. In our case, we implement a destructor, and as we know if we define a destructor, copy constructor and copy assigned function will not be generated.
Let’s take a look at current function which return std::experimental::fundamentals_v1::any
.
any is container with single values of any type, it’s a new type from C++{17}.
In our case we don’t know which type we should return from the polymorphic call, so we return any
type.
//Create vector with any objects as type, and initialize them string, and int values.
std::vector<std::experimental::fundamentals_v1::any> v {std::string("C++"), 17};
//Convert from any type to concret type
std::cout<<std::experimental::fundamentals_v1::any_cast<std::string>(v[0])<<std::endl; //will print 'C++'
std::cout<<std::experimental::fundamentals_v1::any_cast<int>(v[1])<<std::endl; //will print 17
In a code above, I tried to show, how we can use any
type. We create a vector with 2 any
objects inside them which were be converted from std::string
and int
types, and at the end, we convert any
objects back to std::string
and int
types.
Enumerator
class
It’s class for generate permutation, relying to Enumeration
class instances inside them. Hold 1 public function enumerate
, which receive as input argument pointer to function or lambda, it’s kind of Callback
from JS world. For every new enumeration object fire callback with new enumeration.
void enumeration_callback(Enumerations::EnumerationMap enumerationMap){
//First time will fired with height = 180.0, secret letter = 'A'
//Second time with height = 180.0, secret letter = 'B'
//Note it's for our case with locked door.
}
Implementation
class Enumerator {
public:
template<typename Type>
Enumerator &addEnumeration(std::string name, Enumeration<Type> enumeration) {
this->enumeration.push_back({name, std::make_unique<Enumeration<Type> >(enumeration)});
return *this;
}
template<typename Callback>
void enumerate(Callback callback) {
this->enumerate(0, this->enumeration, callback);
}
private:
EnumerationObjects enumeration;
template<typename Callback>
void enumerate(int index_enumeration, EnumerationObjects &enumerations, Callback callback) {
if (index_enumeration == enumerations.size()) {
EnumerationMap enumerationMap;
for (auto &p: enumerations) {
enumerationMap.insert(p.first, p.second->current());
}
if (!enumerationMap.empty())
callback(enumerationMap);
return;
}
while (enumerations[index_enumeration].second->hasNext()) {
enumerate(index_enumeration + 1, enumerations, callback);
enumerations[index_enumeration].second->next();
}
enumerate(index_enumeration + 1, enumerations, callback);
enumerations[index_enumeration].second->reset();
}
};
EnumerationMap
class
It’s just proxy class for std::map<std::string, std::experimental::fundamentals_v1::any
class. This class contain current enumerations for current step in enumeration.
class EnumerationMap {
using Container = std::map<std::string, std::experimental::fundamentals_v1::any>;
private:
Container container;
public:
template<typename Type>
Type get(std::string name) {
return std::experimental::fundamentals_v1::any_cast<Type>(this->container[name]);
}
void insert(std::string name, std::experimental::fundamentals_v1::any val) {
this->container[name] = val;
}
bool empty() {
return this->container.empty();
}
};
Nothing special except convenient conversion from any
class instance to Type
which will be passed as an input argument to function get
.
All together
#include <vector>
#include <map>
#include <iostream>
#include <experimental/any>
#include <memory>
#include <stdexcept>
namespace Enumerations {
class EnumerationBase {
public:
virtual ~EnumerationBase() {
}
EnumerationBase(const EnumerationBase &pb) = default;
EnumerationBase() = default;
virtual std::experimental::fundamentals_v1::any current() = 0;
virtual void next() = 0; // Go to next value
virtual void reset() = 0; // Reset current value to start value
virtual bool hasNext() = 0; // Check we have next value
};
using EnumerationObjects = std::vector<std::pair<std::string, std::unique_ptr<EnumerationBase> >>;
template<class T>
class Enumeration : public EnumerationBase {
public:
Enumeration(const Enumeration &pb) = default;
Enumeration() = default;
virtual ~Enumeration() = default;
virtual std::experimental::fundamentals_v1::any current() override {
return current_val;
}
virtual void next() override {
if (!this->hasNext()) {
throw std::out_of_range("Current enumeration doesn't has next values");
}
current_val += step;
}
virtual void reset() override {
this->current_val = this->start;
}
virtual bool hasNext() override {
return current_val + step <= end;
}
Enumeration(T start, T end, T step) : start(start), current_val(start), end(end), step(step) {}
private:
T start;
T end;
T step;
T current_val;
};
class EnumerationMap {
using Container = std::map<std::string, std::experimental::fundamentals_v1::any>;
private:
Container container;
public:
template<typename Type>
Type get(std::string name) {
return std::experimental::fundamentals_v1::any_cast<Type>(this->container[name]);
}
void insert(std::string name, std::experimental::fundamentals_v1::any val) {
this->container[name] = val;
}
bool empty() {
return this->container.empty();
}
};
class Enumerator {
public:
template<typename Type>
Enumerator &addEnumeration(std::string name, Enumeration<Type> enumeration) {
this->enumeration.push_back({name, std::make_unique<Enumeration<Type> >(enumeration)});
return *this;
}
template<typename Callback>
void enumerate(Callback callback) {
this->enumerate(0, this->enumeration, callback);
}
private:
EnumerationObjects enumeration;
template<typename Callback>
void enumerate(int index_enumeration, EnumerationObjects &enumerations, Callback callback) {
if (index_enumeration == enumerations.size()) {
EnumerationMap enumerationMap;
for (auto &p: enumerations) {
enumerationMap.insert(p.first, p.second->current());
}
if (!enumerationMap.empty())
callback(enumerationMap);
return;
}
while (enumerations[index_enumeration].second->hasNext()) {
enumerate(index_enumeration + 1, enumerations, callback);
enumerations[index_enumeration].second->next();
}
enumerate(index_enumeration + 1, enumerations, callback);
enumerations[index_enumeration].second->reset();
}
};
}
int main(){
Enumerations::Enumerator()
.addEnumeration("secret letter", Enumerations::Enumeration<char>('A', 'Z', 1))
.addEnumeration("height", Enumerations::Enumeration<double>(180.0, 190.0, 0.1 ))
.enumerate([](Enumerations::EnumerationMap enumerationMap) {
std::cout << enumerationMap.get<char>("secret letter") << ' '
<< enumerationMap.get<double>("height") << std::endl;
});
}
That is! We can use this enumerator in many cases, like for permutation, brute-forces, fill databases and list with fake data, generation random data, etc…