2

I am trying to make a macro that will wrap the column specification of a \multicolumn command with @{} if a * is passed to my macro. I can achieve the behaviour I want in the first definition below, but I am trying to figure out how to do it the "smarter" way, as attempted in the second definition, but it throws an error. Why?

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{booktabs}
\usepackage{multirow}
\usepackage{xparse}

%%% this gets me what I want
\NewExpandableDocumentCommand{\sCell}{s O{l} D(){1} m }{%
    \IfBooleanTF#1{%
        \multicolumn{#3}{#2}{#4}%
    }{%
        \multicolumn{#3}{@{}#2@{}}{#4}%
    }%
    }
    
%%% but I am trying to learn how to do it like this
\NewExpandableDocumentCommand{\sCell}{s O{l} D(){1} m }{%
    \IfBooleanTF#1{\def\colspec{#2}}{\def\colspec{@{}#2@{}}}
        \multicolumn{#3}{\colspec}{#4}%
}

\begin{document}

% this is the behaviour I want
\begin{tabular}{ll}\toprule
\sCell{Hello}\\% 
\sCell*{Hello}\\% 
\end{tabular}

\end{document}

1

1 Answer 1

3

\multicolumn is built on top of a TeX primitive called \omit, and if \omit is used in a table cell it has to be the first non-expandable thing in the table cell (besides \omit/\hline). \def is not expandable, so using it in your command before using the \multicolumn will not work because the \def will tell TeX that it doesn't have to look for \omit any longer, then later you give it an \omit and TeX complains.

A simpler example of the issue:

\documentclass{article}
\begin{document}
\begin{tabular}{c}
  % Any of these will cause the error.
  % \def\x{y}
  % \relax
  % a
  \multicolumn{1}{l}{error}
\end{tabular}
\end{document}

That said, it seems you want to write your command without repeating the \multicolumn, so here are two options:

The first one, using an auxiliary. Instead of defining \colspec with the proper argument, pass it to an auxiliary macro that will then use it in the \multicolumn. This works because it uses, besides the \IfBooleanTF which is expandable, a macro which is by definition expandable:

\NewExpandableDocumentCommand{\sCell}{s O{l} D(){1} m }{%
  \IfBooleanTF{#1}%
    {\sCellaux{#2}}%
    {\sCellaux{@{}#2@{}}}%
      {#3}{#4}} % #3 and #4 outside the conditional because they are the same
\newcommand\sCellaux[3]{%
  \multicolumn{#2}{#1}{#3}}

The second option, using a few expl3 auxiliaries, is to force the expansion of \IfBooleanTF, then shuffle things a bit to get the second argument in the right position. This works because it uses some expandable primitives to trigger the expansion, and a macro to swap the arguments:

\ExplSyntaxOn
\NewExpandableDocumentCommand{\sCell}{s O{l} D(){1} m }
  {
    \exp_args:Nf \use_ii_i:nn
      { \IfBooleanTF {#1} { {#2} } { {@{}#2@{}} } }
      { \multicolumn {#3} } {#4}
  }
\ExplSyntaxOff

Here's a compilable example:

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{booktabs}
\usepackage{xparse}

%%% this gets me what I want
\ExplSyntaxOn
\NewExpandableDocumentCommand{\sCell}{s O{l} D(){1} m }
  {
    \exp_args:Nf \use_ii_i:nn
      { \IfBooleanTF {#1} { {#2} } { {@{}#2@{}} } }
      { \multicolumn {#3} } {#4}
  }
\ExplSyntaxOff

%%% this gets me what I want
% \NewExpandableDocumentCommand{\sCell}{s O{l} D(){1} m }{%
%   \IfBooleanTF{#1}%
%     {\sCellaux{#2}}%
%     {\sCellaux{@{}#2@{}}}%
%       {#3}{#4}}
% \newcommand\sCellaux[3]{%
%   \multicolumn{#2}{#1}{#3}}

\begin{document}

% this is the behaviour I want
\begin{tabular}{ll}
\sCell{Hello}\\% 
\sCell*{Hello}\\% 
\end{tabular}

\end{document}

You must log in to answer this question.

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