@AlexeyMalistov gave an answer to make LaTeX draw a \hline between each line in tabular without using \hline? in StackOverflow before TeX.SE existed. The same question was later asked on TeX.SE: Tabular with automatic \hline.

Malistov's answer, which successfully generates a line between each row of a table, is to add this before tabular:

\let \savecr \@tabularcr

I think this redefines the newline character within the tabular environment, so that an \hline is inserted, but the code is still mostly just voodoo to me. Would someone be willing to explain how it works? Thanks.

3 Answers 3


Breaking the code

\let \savecr \@tabularcr

down step-by-step:

  • \catcode`@=11

    This changes the category code for @ to 'letter', since @ may otherwise not be used inside macro names. You'll note that the subsequent line uses \@tabularcr, for which we need @ to be of type 'letter'. Others like to use \makeatletter for this (see What do \makeatletter and \makeatother do?).

  • \let \savecr \@tabularcr

    This makes an immediate copy of \@tabularcr and stores it in \savecr. It allows you to now modify \@tabularcr (subsequent line) yet still have a copy of the older version.

  • \def\@tabularcr{\savecr\hline}

    Here \@tabularcr is actually redefined to be \savecr (stored above as a copy of \@tabularcr before this redefinition followed by \hline - the horizontal rule you're after. Why (re)define \@tabularcr? From the LaTeX kernel (latex.ltx), the definition of the tabular environment executes (at some later stage)

    \def\@tabular{\leavevmode \hbox \bgroup $\let\@acol\@tabacol
       \let\@classiv\@tabclassiv \let\\\@tabularcr\@tabarray}

    Here you can see (in the last line) that \\ is set to be equivalent to \@tabularcr before starting the actual tabular, so \\ will have the new definition of \@tabularcr which now includes an appended \hline.

  • \catcode`@=12

    This restores the category code of @ to other. Some users prefer using \makeatother.

For a reference on what category codes are, see What are category codes?

Of course, the addition of this before a tabular makes it global within your document. It's perhaps more wise to contain this within a group (or as an environment) to localize the change. Alternatively, you can define command switches to "activate" this \hline automation and "deactivate" it later.

  • Thanks. Perfect. Sorry--marking as accepted wasn't enough.
    – Mars
    Commented Aug 30, 2013 at 2:39

That code seems to work, but it doesn't. Let's look at an example:


\let \savecr \@tabularcr


that outputs

enter image description here

Let's see why. The original definition of \@tabularcr is

% latex.ltx, line 5040:

and already something is to be noted: if one, by mistake or because some code is being shared between tabular and longtable, types \\* for ending a row, the * will not be recognized and removed by the redefined command, because the token after \savecr is always \hline.

So the new \@tabularcr leaves in the input stream


and here's where things go wrong again, because the definition of \@xtabularcr is

% latex.ltx, line 5042:

The token next to \@xtabularcr will never be [, and this is the cause for the bad output of my example.

What's the best way to automatically add a rule after every row? None, for at least two reasons:

  1. Horizontal rules are not necessary after every row.
  2. You can't add \cline where you need it.
  • Great--thanks for the additional clarification. I see that though doing something using global commands in this way helps avoid certain errors (because you can modify multiple lines from a single location) but involves tradeoffs because of potential problems later. I love the fact that LaTeX gives me the flexibility to control content and presentation in the way I need, but appreciate that there are limitations that must be taken into account.
    – Mars
    Commented Aug 30, 2013 at 2:47

Using this solution with a longtable results in a few missing rowlines (as explained by egreg). A solution is to set the chunk size of longtable to be greater than the number of rows of your table:


Note: This happens when using pander to generate RMarkdown tables:

pander(df, keep.line.breaks = TRUE, use.hyphening = TRUE, split.table = Inf)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .