Imperative loops

Did you ever write…

for (int i = 0; i < 10; ++i) {
    ...
}

What a stupid question! Of course you have.

So, templatious has some for this too. The upper loop can be replaced with:

TEMPLATIOUS_FOREACH(int i,SF::seqL(10)) {
    ...
}

TEMPLATIOUS_FOREACH macro is a foreach macro that works for everything that specializes collection adapter. SF::seqL method returns a special templatious class - a sequence. It simply contains three parameters (12 bytes in x86)

  • start index
  • end index
  • step

SF::seqL overload stands for “sequence less” as it doesn't include the last member of the sequence. Had we used SF::seqI 10 would also be included.

There are more overloads of sequence methods. They, and corresponding boilerplate loop constructs are shown below:

// start index
for (int i = 1; i < 10; ++i) {
    // i = [1 .. 9]
}

TEMPLATIOUS_FOREACH(int i,SF::seqL(1,10)) {

}


// with step
for (int i = 1; i < 10; i += 2) {
    // i = [1,3,5,7,9]
}

TEMPLATIOUS_FOREACH(int i,SF::seqL(1,10,2)) {

}

Sequence creation methods will adjust step to decrement instead of increment if start is more than end:

TEMPLATIOUS_FOREACH(int i,SF::seqL(10,1)) {
    std::cout << i << " ";
}
// prints:
// 10 9 8 7 6 5 4 3 2 

TEMPLATIOUS_FOREACH(int i,SF::seqI(10,1)) {
    std::cout << i << " ";
}
// prints:
// 10 9 8 7 6 5 4 3 2 1 

Step must always be positive, even if we're iterating backwards:

auto s = SF::seqL(10,1,-2);
// throws templatious::NegativeStepException

Type of the sequence is always inferred. However, if inference is not enough, you can always be more explicit. Begin, end and step are always saved as one type.

auto s1 = SF::seqI('a','z');
TEMPLATIOUS_FOREACH(char i,s1) {
    std::cout << i;
}
std::cout << std::endl;

auto s2 = SF::seqI<char>(97,122);
TEMPLATIOUS_FOREACH(auto i,s2) {
    std::cout << i;
}
std::cout << std::endl;

// prints out:
// abcdefghijklmnopqrstuvwxyz
// abcdefghijklmnopqrstuvwxyz

Any sequence created with StaticFactory can be reversed any time just by calling their .rev() method, which creates a new, reversed sequence:

auto s = SF::seqI('a','z').rev();
TEMPLATIOUS_FOREACH(auto i,s) {
    std::cout << i;
}
std::cout << std::endl;

// prints out:
// zyxwvutsrqponmlkjihgfedcba

Sometimes we might not care about the index of loop, sometimes we might just want to repeat something n times. Templatious has a special macro for that:

TEMPLATIOUS_REPEAT( 3 ) {
    std::cout << "Sup milky?" << std::endl;
}
// prints out:
// Sup milky?
// Sup milky?
// Sup milky?

Templatious repeat macro is one of the most complicated constructs in the templatious library, here is it's definition:

#define TEMPLATIOUS_REPEAT(n) \
    for (long __tmp_i = 0; __tmp_i < n; ++__tmp_i)

Any collection in templatious (including sequences) can be repeated in two ways:

  • absolute repeat
  • multiply repeat

Absolute repeat repeats elements of a collection given amount of times. If amount of repeat times is not divisible by the collection count without a modulus (n % collectionSize != 0) repeat just stops as soon as it's repeat count is exceeded.

auto s = SF::seqI(1,3);
// [1,2,3]

// absolute repeat, 5 times
auto m = SF::repA(5,s);

// absolute repeat, 5 times
TEMPLATIOUS_FOREACH(int i,m) {
    std::cout << i << " ";
}
std::cout << std::endl;

// prints out
// 1 2 3 1 2 

Multiply repeat on the other hand repeats elements of collection n times.

auto s = SF::seqI(1,3);
// [1,2,3]

// multiply repeat, 5 times
auto m = SF::repM(5,s);

// absolute repeat, 5 times
TEMPLATIOUS_FOREACH(int i,m) {
    std::cout << i << " ";
}
std::cout << std::endl;

// prints out
// 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 

Repeater saves collection to repeat as reference so there is no copying (unless, collection to repeat is passed as rvalue reference, then rvalue copy constructor is used to copy collection).

There's another repeater - SF::repS - repeater special, that simply repeats the value given to it n times.

// repeat letter 'a' 7 times.
auto r = SF::repS(7,'a');

TEMPLATIOUS_FOREACH(auto i,r) {
    std::cout << i;
}
std::cout << std::endl;

// prints out:
// aaaaaaa

Now, if you don't like writing TEMPLATIOUS_FOREACH all that much and still don't want to write boilerplate, you can use SM::forEach function to call a function on a collection.

auto s = SF::seqL(3); // [0,1,2]
auto lambda = [](int i) { std::cout << i; };

SM::forEach(lambda,s);

// prints out
// 012

Or on two collections…

SM::forEach(lambda,s,s);

// prints out
// 012012

Or on tree collections with random variables…

SM::forEach(lambda,7,s,7,s,7,s,7);
// prints out
// 7012701270127

You get the idea. Enough about sequences and repeaters, time for more serious toys. If you needed to write nested loops like so:

for (int i = 0; i < 10; ++i) {
    for (int j = 0; j < 10; ++j) {
        for (int k = 0; k < 10; ++k) {
            std::cout << i << j << k << std::endl;
        }
    }
}

// prints out:
// 000
// 001
// 002
// ...
// 997
// 998
// 999

Now you can replace this using StaticManipulator::quadro function, which can quadratically traverse arbitrary sequences (anything that specializes collection adapter).

auto s = SF::seqL(10);
SM::quadro([](int i,int j,int k) {
    std::cout << i << j << k << std::endl;
},s,s,s);

First argument for quadro function is anything, that can be called with loop arguments i,j,k and after that we simply list all the sequences that we want to traverse quadratically. They may differ in types, the most important thing is, you have to be able to call the first thing you passed to the function with the elements of the remaining things. This quadratic traversal is perfectly legal:

std::vector< std::string > v;
SA::add(v,"first","second");
auto s = SF::seqI(1,2);

SM::quadro([](int i,const std::string& s) {
    std::cout << i << " " << s << std::endl;
},s,v);

// prints out:
// 1 first
// 1 second
// 2 first
// 2 second

However, if you happen to flip the order of arguments may the compiler error message have mercy on your soul:

SM::quadro([](const std::string& s,int i) {
    std::cout << i << " " << s << std::endl;
},s,v);

At least, if it compiled you know you have done something right. Another thing, quadro function assumes that you don't remove or add elements to the collections during traversal, that would be an undefined behaviour.

There's no limit to the amount of arguments quadro function can process (thank you, variadic templates of C++11), so, for fun, let's generate 17th level quadratic loop which simply prints numbers from 0 to 65535 in binary form:

auto s = SF::seqI(0,1);
auto endline = SF::repS(1,'\n'); // end line symbol
auto sf = SF::streamOutFunctor(std::cout);
// sf(args...) streams arbitrary
// amount of arguments to std::cout

SM::quadro(sf,
    s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,endline);

Have fun with looping!