Pre-calculate the winning combinations for an n-sized tic tac toe board using C++ functional programming This code pre-calculates the winning combinations for an n-sized tic tac toe board.

I first created my function using an imperative approach.  This is just how I naturally write code most of the time.  I then tried to think about the problem using a "What I want to accomplish?" approach, rather than "What do I want it to do?"  In the end I think I still ended up with an imperative approach, just using STL algorithms as loops.  

Imperative approach:

std::vector&lt;std::vector&lt;int&gt;&gt; rules3(const int x){
    using namespace std;

    vector&lt;vector&lt;int&gt;&gt; seqs;
    for(int n=0; n&lt;x; ++n){
        vector&lt;int&gt; seq;
        for(int m=0; m&lt;x; ++m){
            seq.push_back(n*x+m);
        }
        seqs.push_back(seq);
    }
    for(int n=0; n&lt;x; ++n){
        vector&lt;int&gt; seq;
        for(int m=0; m&lt;x; ++m){
            seq.push_back(n+x*m);
        }
        seqs.push_back(seq);
    }
    vector&lt;int&gt; seq;
    for(int n=0; n&lt;x; ++n){
        seq.push_back(n*x+n);
    }
    seqs.push_back(seq);
    seq.clear();
    for(int n=0; n&lt;x; ++n){
        seq.push_back(x-1+n*(x-1));
    }
    seqs.push_back(seq);

    return seqs;
}


Getting more functional:

std::vector&lt;std::vector&lt;int&gt;&gt; rules2(const int x){
    using namespace std;

    vector&lt;vector&lt;int&gt;&gt; seqs;
    vector&lt;int&gt; iter(x);
    iota(iter.begin(), iter.end(), 0);

    for(auto n: iter){
        vector&lt;int&gt; seq;
        for(auto m: iter){
            seq.push_back(n*x+m);
        }
        seqs.push_back(seq);
    }
    for(auto n: iter){
        vector&lt;int&gt; seq;
        for(auto m: iter){
            seq.push_back(n+x*m);
        }
        seqs.push_back(seq);
    }
    vector&lt;int&gt; seq;
    seq.clear();
    for(auto n: iter){
        seq.push_back(n*x+n);
    }
    seqs.push_back(seq);
    seq.clear();
    for(auto n: iter){
        seq.push_back(x-1+n*(x-1));
    }
    seqs.push_back(seq);

    return seqs;
}


Functional approach? (still feels imperative):

std::vector&lt;std::vector&lt;int&gt;&gt; rules1(const int x){
    using namespace std;

    vector&lt;vector&lt;int&gt;&gt; seqs;
    vector&lt;int&gt; iter(x);
    iota(iter.begin(), iter.end(), 0);

    transform(iter.begin(), iter.end(), back_inserter(seqs), [&amp;](const int&amp; n){
        vector&lt;int&gt; seq;
        transform(iter.begin(), iter.end(), back_inserter(seq), [&amp;](const int&amp; m){
            return n*x+m;
        });
        return seq;
    });
    transform(iter.begin(), iter.end(), back_inserter(seqs), [&amp;](const int&amp; n){
        vector&lt;int&gt; seq;
        transform(iter.begin(), iter.end(), back_inserter(seq), [&amp;](const int&amp; m){
            return n+x*m;
        });
        return seq;
    });
    vector&lt;int&gt; seq;
    transform(iter.begin(), iter.end(), back_inserter(seq), [&amp;](const int&amp; n){
        return n*x+n;
    });
    seqs.push_back(seq);
    seq.clear();
    transform(iter.begin(), iter.end(), back_inserter(seq), [&amp;](const int&amp; n){
        //return (x-n-1)*x+n; // 6,4,2
        return x-1+n*(x-1); // 2,4,6
    });
    seqs.push_back(seq);

    return seqs;
}


Output when printing:

rules3
std::vector(0, 1, 2)
std::vector(3, 4, 5)
std::vector(6, 7, 8)
std::vector(0, 3, 6)
std::vector(1, 4, 7)
std::vector(2, 5, 8)
std::vector(0, 4, 8)
std::vector(2, 4, 6)
rules2
std::vector(0, 1, 2)
std::vector(3, 4, 5)
std::vector(6, 7, 8)
std::vector(0, 3, 6)
std::vector(1, 4, 7)
std::vector(2, 5, 8)
std::vector(0, 4, 8)
std::vector(2, 4, 6)
rules1
std::vector(0, 1, 2)
std::vector(3, 4, 5)
std::vector(6, 7, 8)
std::vector(0, 3, 6)
std::vector(1, 4, 7)
std::vector(2, 5, 8)
std::vector(0, 4, 8)
std::vector(2, 4, 6)
Here are some suggestions.  This answers uses the Range-v3 library and assumes

#include &lt;range/v3/all.hpp&gt;

namespace view = ranges::view;



Please include the #includes and supply a small test program in the future.  You probably have written them anyway, so why not post them to save reviewers' time? :)
int may be too small for indexes.  Consider using std::size_t.  You can define a type alias for flexibility.

using index_t = std::size_t;

The type std::vector&lt;std::vector&lt;index_t&gt;&gt; occurs many times.  Save time by writing

using rule_t = std::vector&lt;index_t&gt;
using rules_t = std::vector&lt;rule_t&gt;;

The concept "\$n \times n\$ board" only makes sense when \$n\$ is a positive integer.  Therefore, enforce the pre-condition \$n \ge 1\$.  Also, the name x is a bit vague.  size may be better.

rules_t rules(index_t size)
{
    if (size == 0)
        throw std::invalid_argument{"..."};
    // ...
}

rules(1) currently returns {{0}, {0}, {0}, {0}}, which is definitely wrong.  It should return {{0}}.  Your general logic is "\$n\$ rows + \$n\$ columns + \$2\$ diagonals", which only applies to \$n \ge 2\$.  The easiest approach would be a special case:

rules_t rules(index_t size)
{
    // ...
    else if (size == 1)
        return {{0}};
}

Following the previous bullet, why not make separate functions to make your logic clear?

rules_t rules(index_t size)
{
    // ...
    else {
        auto rows = rules_row(size);
        auto columns = rules_column(size);
        auto diagonals = rules_diagonal(size);
        return ranges::to&lt;rules_t&gt;(view::concat(rows, columns, diagonals));
    }
}


where view::concat concatenates the three vectors and ranges::to converts the result to the desired return type.  (Note that view::concat is disabled with rvalues to prevent dangling iterators, so we first store the subvectors.)
The rules_row function is easy to write:

rules_t rules_row(index_t size)
{
    return view::ints(index_t(0), size * size) | view::chunk(size);
}


view::ints generate a left-inclusive sequence of integers {0, 1, 2, ..., size * size - 1}, and view::chunk(size) breaks it into chunks each of size size.
The rules_column function is a bit tricky, because the Range library does not provide a function like chunk that splits like this.  We do have a function stride, so we can write a manual "loop":  (it took me quite a while to figure this out, so tell me if there is a better way!)

rules_t rules_column(index_t size)
{
    return view::ints(index_t(0), size) |
               view::transform([=](index_t col) {
                   return view::ints(index_t(col), size * size) | view::stride(size);
               });
}


view::ints(index_t(0), size) generates the column numbers.  Each of them is passed to the lambda.  The lambda returns the corresponding rule for each column.
The rules_diagonal function is moderately simple:

rules_t rules_diagonal(index_t size)
{
    return {
        view::ints(index_t(0), size) |
            view::transform([=](index_t r) { return r * (size + 1); }),
        view::ints(index_t(1), size + 1) |
            view::transform([=](index_t r) { return r * (size - 1); })
    };
}


Here, we always have two rules: one for the primary diagonal

{0 * size + 0, 1 * size + 1, 2 * size + 2, ..., (size - 1) * size + (size - 1)}


which is equivalent to

{0, 1, 2, ..., size - 1} * (size + 1)


and the other for the secondary diagonal

{size - 1, 2 * size - 2, 3 * size - 3, ... size * size - (size - 1)}


which is equivalent to

{1, 2, 3, ..., size + 1} * (size - 1)

Putting everything together:

#include &lt;vector&gt;
#include &lt;range/v3/all.hpp&gt;

namespace view = ranges::view;

using index_t = std::size_t;
using rule_t = std::vector&lt;index_t&gt;;
using rules_t = std::vector&lt;rule_t&gt;;

rules_t rules_row(index_t size)
{
    return view::ints(index_t(0), size * size) | view::chunk(size);
}

rules_t rules_column(index_t size)
{
    return view::ints(index_t(0), size) |
               view::transform([=](index_t col) {
                   return view::ints(index_t(col), size * size) | view::stride(size);
               });
}

rules_t rules_diagonal(index_t size)
{
    return {
        view::ints(index_t(0), size) |
            view::transform([=](index_t r) { return r * (size + 1); }),
        view::ints(index_t(1), size + 1) |
            view::transform([=](index_t r) { return r * (size - 1); })
    };
}

rules_t rules(index_t size)
{
    if (size == 0)
        throw std::invalid_argument{"A board cannot have size zero"};
    else if (size == 1)
        return {{0}};
    else {
        auto rows = rules_row(size);
        auto columns = rules_column(size);
        auto diagonals = rules_diagonal(size);
        return view::concat(rows, columns, diagonals);
    }
}


(live demo, tests the function for size = 1, 2, 3, ..., 10)