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 case Enumeration 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 all Enumeration class instances, and has function enumerate.
    • 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 instance EnumerationMap which hold value for the current enumeration.
  • class EnumerationMap hold values for current enumeration,also they has function for insert value by std::string as key and any as value, function for get value by std::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…