Using
\newcommand{\foo}[...][...]
creates a macro \foo
which has definition
\@protected@testopt \foo \\foo {...}
Inside a LaTeX 'protected expansion' command (\protected@edef
, \protected@xdef
, \protected@write
), the definition of \@protected@testopt
is altered such that no further expansion takes place, so that
\protected@edef\test{\foo}
\show\test
gives
> \test=macro:
->\protect \foo .
\DeclareRobustCommand
works slightly differently, as
\DeclareRobustCommand{\foo}[...][...]
gives a definition
> \foo=macro:
->\protect \foo .
where there is a space in the name of that internal macro: it's called '\foo
'. Doing a 'protected expansion' here gives
> \test=macro:
->\protect \foo .
i.e. the 'name with space' is retained. As this method adds \protect
'earlier' than the mechanism used by \newcommand
, this is more robust at the cost of an additional cname.
Neither of these mechanisms will prevent expansion inside a plain \edef
, \xdef
or \write
. For that, you need the e-TeX protected mechanism, which is wrapped up in a \newcommand
-like way by \newrobustcmd
from the etoolbox
package. There, doing
\newrobustcmd{\foo}[...][...]
gives a definition
> \foo=\protected macro:
->\@testopt \\foo {...}.
This will never expand in an expansion context, as the engine is doing the protection.
Well not completely gone, but you have to try a lot harder to find a case, and if it's a really plausible case quite possibly we'd accept an enhancement request to make the command robust.
For example \begin
has been robust (only since 2019, not 2015) so \begin{tabular}
is robust, as is \\
(from 2015) but \cline
is not, compare these two \typeout
\documentclass{article}
\begin{document}
\typeout{
\begin{tabular}{cc}
a&b\\
\cline{1-1}
c&d
\end{tabular}}
\typeout{
\begin{tabular}{cc}
a&b\\
\protect\cline{1-1}
c&d
\end{tabular}}
\end{document}
producing
\begin {tabular}{cc} a&b\\ \omit \@multicnt 1\advance \@multicnt \m@ne \relax
\def \iterate {}\iterate \let \iterate \relax \@multicnt 1\advance \@multicnt -
1\advance \@multicnt \@ne \relax \def \iterate {}\iterate \let \iterate \relax
\leaders \hrule height\arrayrulewidth \hfill \cr \noalign {\vskip -\arrayrulewi
dth } c&d \end {tabular}
and
\begin {tabular}{cc} a&b\\ \cline{1-1} c&d \end {tabular}
Of course \typeout
here could be replaced by any command with moving argument such as caption or section hesdings.
Best Answer
The command you defined does not take an optional argument, it takes a delimited argument. If you do:
it will work fine, however if you remove the
[one]
TeX will throw an error:because when you define a command with
\def\b[#1]#2{.#2.\bf #1}
, TeX expects that when you use\b
, the input matches exactly the parameter text (i.e.,[#1]#2
), which means that the next token must be[
, and when it is not that error is raised. See here for a brief description of that.When using just
\def
none of the arguments are optional! However, let's say you define:then the command will have
2
arguments, the first of which is optional, and if not given the default value is--empty--
. When you use\b
, the defined command does not actually take any argument, but it checks if the next character is a[
. If it is, the command proceeds to use an "inner"\b
(let's call it\b@opt
), which is defined as you did, with\def\b@opt[#1]#2{.#2.\bf #1}
. However if you use\b
without the following[
, then a\b@noopt
is used, which is defined as\def\b@noopt{\b@opt[--empty--]}
. So after all you end up using\b@opt
, but the underlying definition provides the optional argument if you don't give it one.You can manually define that with:
Now, what makes the optional argument thing “fragile”?
A command is fragile when it can't work properly in an expansion-only context, which is usually when being written to a temporary file, like in section headings as you showed, captions, and the like, but also inside an
\edef
or, more recently, in\expanded
.It's said that commands with optional arguments are fragile because the mechanism that checks if an optional argument is there (precisely, the
\@ifnextchar
macro above) usually is fragile. It is possible, under some restrictions, to check for an optional argument expandably, like inxparse
's\NewExpandableDocumentCommand
, but usually that's not the case.Taking the command defined above as example, if you do
\edef\test{\b[one]{two}}
(or\write
or\expanded
) TeX starts expanding from left to right, so the first thing it sees is\b
, which is expanded toNext the
\@ifnextchar
test is expanded to:Here the problem appears.
\let
,\def
, and\futurelet
aren't expandable, so TeX leaves them as they are, and proceeds expanding the rest. All other macros there will be expanded by TeX, but by doing so the\let
and\def
will not define\reserved@d
and such, but their expansion, and this will make the code not work as intended.Of course this is just an example, but the basic principle of fragility is that a command that contains non-expandable tokens is being used in an expansion-only context.
How to make a command robust?
Until a couple decades ago the only way to make a command robust was to prevent its expansion with
\noexpand\command
, which makes TeX temporarily treat\command
as unexpandable and skip it in an expansion-only context. The downside of this is that as soon as the expansion was carried out the\noexpand
would disappear and the command would be fragile again.To circumvent this LaTeX defines
\protect
and the accompanying macros\protected@edef
and\protected@write
, which define\protect
as\def\protect{\noexpand\protect\noexpand}
. Then, in an expansion-only context\protect\command
will expand to\noexpand\protect\noexpand\command
. TeX will throw both\noexpand
s away, temporarily making\protect\command
both unexpandable. If you happened to use the command again, it would continue being robust if you used the\protected@...
macros instead of the normal ones.Commands with optional arguments defined with LaTeX2ε's
\newcommand
and the like have a different look (but the same machinery underneath). If you define\newcommand\b[2][--empty--]{.#2.\bf #1}
, then\b
will actually be\protected@testopt \b \\b {--empty--}
(that\\b
is the command\\b
, with two backslashes, not\\
thenb
).\protected@testopt
will use the\protect
machinery to test whether it can be safely expanded. If it cannot it will leave\protect\b
, otherwise it will proceed to use\\b
, which contains the actual definition of the command.All this became easier when ε-TeX introduced the
\protected
primitive, which allows you to make a macro engine-protected. This means that instead of tricking TeX to\noexpand
your macro, you will define the macro as robust with:and then TeX itself will know that
\b
is not supposed to be expanded inside an\edef
or\write
or\expanded
, without additional machinery.LaTeX2ε doesn't use
\protected
to define robust macros because of backwards compatibility. LaTeX2ε predates ε-TeX, so the protection mechanism was established much earlier. LaTeX3, for example, dropped the 2ε protection mechanism and uses only\protected
to define robust macros.As a side note, I'd change that definition of yours to:
and use as:
I changed the command to
\mybold
, as one-letter command names are not generally a good idea. I also changed\bf
(which is deprecated for decades now) to\textbf
and put the second argument in braces, so that the second argument istwo
, not justt
.