Before answering How, which does not have a unique answer (your solution is a fine working answer), I will try to document Why your initial attempt failed. This knowledge could be handy to analyse more complicated situations.
First of all I recall that the crucial point is that you are trying to expand some macro from inside the tabular.
tabular cells create groups, hence all assignments should be made global. I would thus recommend here to use LaTeX counters as in LaTeX they are always managed globally.
it is not possible from inside a tabular cell to use a pre-defined TeX delimited macro (having a definition such as \def\foo #1\repeat{stuff with #1}
) if #1
will get a visible &
: \foo A & B\repeat C
, except if \foo
is the very first thing in the cell (meaning that nothing non-expandable like a \relax
or a \def
or a \setcounter
appeared before).
Fixes:
if you need to make definitions or assignments which will influence further cells, you must make them global,
if you need to fetch the &
as a part of a delimited macro it must be somehow hidden, typically inside a braced group, or written as \TAB
with a prior \def\TAB{&}
or in something like \firstofone{&}
(\def\firstofone #1{#1}
).
If we were to fix your original code according to these requirements, would it solve the problem?
Not yet, because the LaTeX loop construct does a local \def
:
$ latexdef loop
\loop:
\long macro:#1\repeat ->\def \iterate {#1\relax \expandafter \iterate \fi }\iterate \let \iterate \relax
Your own answer gives a correct way out of this : you use all of your things in a single cell, the preparation of the macro with the tokens to be inserted being done entirely via \g@addto@macro
. This solves efficiently your problem, and you can use \count
's rather than counters. What about point 2. above? well the &
is appropriately hidden in the \emttok{&}
(this was not necessary for the external loop, but the inner \loop
without it would have seen the naked &
).
Let me briefly discuss another way, which does not proceed by construction of a \mttok
, and tries to work row after row, cell after cell. The most elegant is some expandable loop, but let's stick first with LaTeX's \loop
and see what can be done.
First we need to let it to a global definition of \iterate
. But immediately we hit against the problem of nesting. With this approach, the only way is to define two distinct loop macros, for example \gloop
and \ggloop
. Before doing so, there is more problem to mention.
There is an issue with the \relax
after the #1
in the definition of
\iterate
. For example this will do \ifnum 1=1\relax\expandafter\iterate\fi
. But this is catastropic because the \relax
will
properly terminate the test but it will stay in the token stream, and after a
\\
this forces the start of a new row. Thus there will be a problem with an empty partial row, at the bottom of the tabular. We must get rid of the \relax
. We could replace it by a space to be certain to terminate an \ifnum
test, or we make sure we always use \ifnum
with self-terminating numbers, moving the constraint on the caller side. Doing all this we end up with this approach:
\documentclass{article}
\newcommand*\TAB{&}
% alternative to LaTeX's loop, for use in contexts were definitions
% must be made global to survive
% Thus we do global things, and we use distinct terminators to allow nesting.
% Careful that tests at the end of #1 must be self-terminating
\long\def\gloop #1\grepeat
{\gdef \giterate {#1\expandafter \giterate \grepeat }\giterate }
\long\def\ggloop #1\ggrepeat
{\gdef \ggiterate {#1\expandafter \ggiterate \ggrepeat }\ggiterate }
\let\grepeat\fi
\let\ggrepeat\fi
\newcounter{mt}
\newcounter{mtt}
\newcommand{\mttab}[2]{%
\setcounter{mt}{0}%
\gloop
\stepcounter{mt}% careful with spaces from line endings
\setcounter{mtt}{1}%
(\themt) % to see what happens
\ggloop
\themtt % again to see where we are
\stepcounter{mtt}%
\TAB
\ifnum #1>\value{mtt}%
\ggrepeat
\themtt
\\
\ifnum #2>\value{mt}%
\grepeat
}
\begin{document}
\begin{tabular}{|c|c|c|}
\hline
col1 & col2 & col3\\
\hline
\mttab{3}{15}
\hline
\end{tabular}
\end{document}
This produces (I know you asked for empty cells, but I added contents to visualize things):

The yet more elegant way is proceed with an expandable loop. LaTeX has one: \@whilesw
. This answer explained how to use it. Here it would give the following approach:
\documentclass{article}
\newcounter{mt}
\newcounter{mtt}
% recycling the \@whilesw of LaTeX into a num test
% \ifnum tests must be properly self-terminating
% I define the \xwhilenum slightly differently from my earlier use of it
% https://tex.stackexchange.com/a/142562/4686
\makeatletter
\newcommand*{\xwhilenum}[1]{\@whilesw{\ifnum #1}\fi }
\makeatother
% for reference, the LaTeX kernel definitions:
%\@whilesw:
%\long macro:#1\fi #2->#1#2\@iwhilesw {#1#2}\fi \fi
%\@iwhilesw:
%\long macro:#1\fi ->#1\expandafter \@iwhilesw \else \@gobbletwo \fi {#1}\fi
\newcommand{\mttab}[2]{%
\setcounter{mt}{#2}%
\xwhilenum{\value{mt}>0 }% space after 0 is important
{% must use braces here
\addtocounter{mt}{-1}%
\setcounter{mtt}{#1}%
(\themt) % to see where we are
\xwhilenum{\value{mtt}>1 }% careful, N cells = N-1 &
{%
\themtt % again to see where we are
\addtocounter{mtt}{-1}%
&
}% end of inner \xwhilenum
\themtt
\\
}% end of external \xwhilenum
}
\begin{document}
\begin{tabular}{|c|c|c|}
\hline
col1 & col2 & col3\\
\hline
\mttab{3}{15}
\hline
\end{tabular}
\end{document}
It gives (for a change I counted downwards):

A yet more elegant way perhaps is to dispense completely from using counters: one can do purely expandable loops thanks to \numexpr
. But my answer is already a bit long, thus I will stop here.