5

I want to change the behavior of my macro depending on whether an optional argument is not given at all or is explicitly stated (using []) but empty.

MWE could look like this (stolen from this answer):

\documentclass{article}
\usepackage{ifthen}

\newcommand{\mymacro}[1][]{
\ifthenelse{\equal{#1}{}}{not stated}{explicitly stated but empty}
}

\begin{document}

The optional argument is \mymacro.

The optional argument is \mymacro[].

\end{document}

I want the result to be:

The optional argument is not stated.
The optional argument is explicitly stated but empty.

How do I have to change the \ifthenelse condition? Is this possible at all within \newcommand?

1
  • 1
    Welcome to TeX.SE!
    – Mensch
    Oct 4, 2021 at 14:51

3 Answers 3

6
\documentclass{article}
\usepackage{ifthen}

\newcommand{\mymacro}[1][\noexpand\empty]{%
\ifthenelse{\equal{#1}{}}{explicitly stated but empty}{%
  \ifthenelse{\equal{#1}{\noexpand\empty}}{not stated}{something else}}%
}

\begin{document}

The optional argument is \mymacro.

The optional argument is \mymacro[].

The optional argument is \mymacro[X].
\end{document}

enter image description here

One does not even need ifthen:

\documentclass{article}

\newcommand{\mymacro}[1][\empty]{%
  \ifx\empty#1not stated\else%
  \ifx\relax#1\relax explicitly stated but empty%
  \else something else\fi\fi
}

\begin{document}

The optional argument is \mymacro.

The optional argument is \mymacro[].

The optional argument is \mymacro[X].
\end{document}
1
  • Thanks for the rapid answer! I will accept this one because it is very straightforward. Works in my use case as expected.
    – phoeller
    Oct 4, 2021 at 16:12
6

A job for ltcmd:

\documentclass{article}
% For older kernels
\ifdefined\NewDocumentCommand\else
  \usepackage{xparse}
\fi
%
\ExplSyntaxOn
\cs_new_eq:NN \IfBlankTF \tl_if_blank:nTF
\ExplSyntaxOff

\NewDocumentCommand\mymacro{o}{%
  \IfNoValueTF{#1}{not stated}{\IfBlankTF{#1}{explicitly stated but empty}{WHATNOT}}%
}

\begin{document}

The optional argument is \mymacro.

The optional argument is \mymacro[].

\end{document}
2
  • Why not just load xparse directly? The worst thing that can happen is that it also loads g-type arguments, which you can then just ignore.
    – Gaussler
    Oct 4, 2021 at 18:22
  • 1
    @Gaussler Because in the longer term it's best not to load packages you are not using
    – Joseph Wright
    Oct 4, 2021 at 18:23
3

When defining a command with optional argument via \newcommand, then within the definition-text of that command you cannot distinguish the case of the default-value of the optional argument being provided explicitly from the case of the default-value being inserted due to no optional argument being provided at all.

This also applies when the default-value is "empty" as is the case with your scenario.

This is because when defining a command \mymacro with optional argument via \newcommand, then \mymacro actually is defined to take into account the expansion-control provided by the \protect-concept and, in case expansion shall not be prevented, just start the LaTeX 2ε-kernel-"mechanism" (formed by the kernel-macros \@protected@testopt, \@testopt, \@x@protect and \kernel@ifnextchar) for detecting the presence of the opening [ of an explicitly provided optional argument and calling a macro \\mymacro appropriately, i.e., in the case of the optional argument not being provided explicitly calling the macro \\mymacro with the tokens forming the default value of the optional argument appended, these tokens being nested in curly braces and square-brackets. \\mymacro in turn is a macro which is delimited by [ and whose first argument is delimited by ]and which gathers the arguments and delivers the replacement-text.

The LaTeX 2ε-kernel-mechanism for detecting the presence of an optional argument and appropriately calling \\mymacro does not pass on to \\mymacro information about the coming-into-being of the optional argument, i.e., does not not pass on to \\mymacro information about whether

  • either the presence of an optional argument was not detected and thus there was need to append the default-value, nested in curly braces and square-brackets,
  • or the presence of an optional argument was detected and thus there was no need to append the default-value, nested in square-brackets, as something can be gathered that was provided explicitly.

One approach to the matter, probably suitable in case you work on an older platform where \NewDocumentCommand and xparse and the like infrastructure is not available, can be:

Define your own variants of \@protected@testopt and \@testopt which pass on this information to \\mymacro by means of a separate argument and use these variants for performing "by hand" the defining of \mymacro and \\mymacro analogous to the way in which this would be done for you by \newcommand:

\documentclass{article}

\makeatletter
\newcommand*\MY@protected@testopt[1]{%
  \ifx\protect\@typeset@protect\expandafter\MY@testopt\else\@x@protect#1\fi
}%
\newcommand\MY@testopt[2]{%
  \kernel@ifnextchar[{#1{1}}{#1{0}[{#2}]}%
}%
\expandafter\newcommand\expandafter\mymacro\expandafter{%
  \expandafter\MY@protected@testopt\expandafter\mymacro
  \csname\string\mymacro\endcsname {DEFAULT}%
}%
\expandafter\@ifdefinable\expandafter{\csname\string\mymacro\endcsname}{%
  \long\expandafter\def\csname\string\mymacro\endcsname#1[#2]#3#4{%
    % #1 - flag denoting whether optional argument is 
    %      due to TeX inserting the default-value=0 /
    %      due to TeX gathering something that was provided explicitly=1
    % #2 - what is used as optional argument
    % #3 - first non-optional argument
    % #4 - second non-optional argument
    The optional argument is \ifnum#1=0 not provided\else provided explicitly\fi.\\
    As optional argument used is ``#2''.\\
    The optional argument
    \begingroup
    \toks@{DEFAULT}\edef\tempa{\the\toks@}%
    \toks@{#2}\edef\tempb{\the\toks@}%
    \expandafter\endgroup
    \ifx\tempa\tempb\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {equals}{does not equal} the default.\\
    As second argument you provided ``#3''.\\
    As third argument you provided ``#4''.%
  }%
}%
\makeatother

\begin{document}

\noindent\mymacro{A}{B}

\bigskip

\noindent\mymacro[something]{A}{B}

\bigskip

\noindent\mymacro[DEFAULT]{A}{B}

\end{document}

enter image description here

A shortcoming both of optional arguments via \newcommand and of this approach is that \kernel@ifnextchar is used for detecting the presence of an explicit [ of catcode 12(other).
\kernel@ifnextchar in turn only looks at the meaning of the next token and therefore can be fooled by using an implicit [ of catcode 12(other).

E.g., with \let\lbracket=[ you would probably expect \mymacro\lbracket{A} to yield

enter image description here

, but this won't work out. Instead you get a bunch of weird error-messages.

You can easily resolve this by adding a check for explicit [:

\documentclass{article}

\makeatletter
\newcommand\ExplicitImplicitFork[3]{%
  \ForkExplicitImplicit!#3!{#1}![!{#2}!!!!#3%
}%
\@ifdefinable\ForkExplicitImplicit{%
  \long\def\ForkExplicitImplicit#1![!#2#3!!!!{#2}%
}%
\newcommand*\MY@protected@testopt[1]{%
  \ifx\protect\@typeset@protect\expandafter\MY@testopt\else\@x@protect#1\fi
}%
\newcommand\MY@testopt[2]{%
  \kernel@ifnextchar[{\ExplicitImplicitFork{#1{1}}{#1{0}[{#2}]}}{#1{0}[{#2}]}%
}%
\expandafter\newcommand\expandafter\mymacro\expandafter{%
  \expandafter\MY@protected@testopt\expandafter\mymacro
  \csname\string\mymacro\endcsname {DEFAULT}%
}%
\expandafter\@ifdefinable\expandafter{\csname\string\mymacro\endcsname}{%
  \long\expandafter\def\csname\string\mymacro\endcsname#1[#2]#3#4{%
    % #1 - flag denoting whether optional argument is 
    %      due to TeX inserting the default-value=0 /
    %      due to TeX gathering something that was provided explicitly=1
    % #2 - what is used as optional argument
    % #3 - first non-optional argument
    % #4 - second non-optional argument
    The optional argument is \ifnum#1=0 not provided\else provided explicitly\fi.\\
    As optional argument used is ``#2''.\\
    The optional argument
    \begingroup
    \toks@{DEFAULT}\edef\tempa{\the\toks@}%
    \toks@{#2}\edef\tempb{\the\toks@}%
    \expandafter\endgroup
    \ifx\tempa\tempb\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {equals}{does not equal} the default.\\
    As second argument you provided ``#3''.\\
    As third argument you provided ``#4''.%
  }%
}%
\makeatother

\begin{document}

\noindent\mymacro{A}{B}

\bigskip

\noindent\mymacro[something]{A}{B}

\bigskip

\noindent\mymacro[DEFAULT]{A}{B}

\bigskip

\let\lbracket=[
\noindent\mymacro\lbracket{A}

\end{document}

enter image description here

Unlike with the approach discussed as the next one with this approach the mechanism for detecting whether the optional argument is due to TeX gathering something that was provided explicitly or is due to TeX inserting the default-value cannot be fooled by explicitly providing a special default-value.
But this is at the cost of a macro argument holding information about how the optional argument in question came into being.


Another approach to the matter is using as default-value of the optional argument in question a special sequence of tokens which is to denote that the optional argument is not due to TeX gathering something that was provided explicitly but is due to TeX inserting that default-value.
Inside the definition-text of the macro processing the optional argument in question directives can be noted for checking whether the token-sequence currently forming the optional argument in question equals that special sequence of tokens.

If you do this approach, then the mechanism for detecting whether the optional argument is due to TeX gathering something that was provided explicitly or is due to TeX inserting the default-value can be fooled by explicitly providing that default-value/that special sequence of tokens.

 

This approach can be realized, e.g., using \NewDocumentCommand with an o-type-argument and \IfNoValueTF for querying "the presence of an optional argument".

\NewDocumentCommand, o-type-arguments and \IfNoValueTF are provided by the package xparse and are also incorporated into more recent releases of the LaTeX 2ε-kernel.

With o-type-arguments the default-value/the special sequence of tokens denoting that the optional argument in question is not due to TeX gathering something that was provided explicitly but is due to TeX inserting that default-value is the token-sequence -11, N11, o11, V11, a11, l11, u11, e11, -12—the catcode of the first - deviates from the usual catocode-setting. (This token-sequence comes from expanding expl3's macro \c_novalue_tl which denotes the marker for no value being provided explicitly.) Checking whether the token-sequence currently forming the argument in question equals that special sequence of tokens is done via \IfNoValueTF.

o-type-argument is the same as O{⟨default⟩}-type-argument with ⟨default⟩ = ⟨expansion of \c_novalue_tl⟩.

You can easily fool the \IfNoValueTF-mechanism by providing this token-sequence explicitly.

An example using \NewDocumentCommand with o-type-argument and querying via \IfNoValueTF is already provided in the answer of Joseph Wright.

With the definition of \mymacro as provided in Joseph Wright's example try, e.g.,

the optional argument is
\expandafter\expandafter\expandafter\mymacro
\expandafter\expandafter\expandafter[%
\csname c_novalue_tl\endcsname]

or

\begingroup
\lccode`\x=`\-
the optional argument is \lowercase{\mymacro[x}NoValue-]
\endgroup

In both cases you will get the phrase the optional argument is not stated although the optional argument is provided explicitly.

 

With the variants of Steven B. Segletes the special sequence of tokens denoting that the optional argument is not due to TeX gathering something that was provided explicitly but is due to TeX inserting the default-value is formed by the single token \empty, whose expansion needs to be prevented in the first example.

Here, too, the mechanisms for checking whether the optional argument is due to TeX gathering something that was provided explicitly or is due to TeX inserting the default-value can be fooled by providing that special sequence of tokens explicitly:

With the definition of \mymacro, as provided in the first example, try, e.g., the optional argument is \mymacro[\noexpand\empty]—you get the phrase the optional argument is not stated although the optional argument is provided explicitly.

With the definition of \mymacro, as provided in the second example, try, e.g., the optional argument is \mymacro[\empty]—you get the phrase the optional argument is not stated although the optional argument is provided explicitly.

Be aware that lightheartedly using macro-arguments for completing \if..-expressions may lead to unexpected results:

With the definition of \mymacro, as provided in the second example

  • the optional argument is \mymacro[\empty some badness ] yields the optional argument is some badness not stated.

  • the optional argument is \mymacro[\empty\fi\iftrue] yields the optional argument is not stated although the optional argument is provided explicitly.

  • the optional argument is \mymacro[\relax] yields the optional argument is explicitly stated but empty although the optional argument is not empty but is \relax.

You must log in to answer this question.

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