9

Just when I think I starting to understand \expandafter I run into this problem where I am trying to test if \ConditionG, \ConditionH, etc are defined in a loop. I tried all the combinations I could think of and am not able to get this to work:

\documentclass{article}
\usepackage{pgffor}
\usepackage{etoolbox}

\begin{document}

%\def\ConditionG{}% This should show up as undefined
\def\ConditionH{}

\foreach \i in {G, H}{
    \expandafter\ifdefined\csname Condition\i\endcsname% Incorrect Results: everything defined
        \textbackslash Condition\i\ is defined\par
    \else
        \textbackslash Condition\i\ is not defined\par
    \fi
}

\hrule
\foreach \i in {G, H}{% Using etoolbox
    \ifdef{\expandafter\csname Condition\i\endcsname}{% Incorrect Results: everything defined
        \textbackslash Condition\i\ is defined\par
    }{
        \textbackslash Condition\i\ is not defined\par
    }
}

\end{document}

Joseph's detailed explanation regarding When to use \edef, \noexpand, and \expandafter? makes sense to me, but can't seem to apply that to my problem.

3 Answers 3

7

You should use \ifcsname ... \endcsname instead.

Note that \csname ... \endcsname makes the undefined control sequence to be \relax. See TeXbook Exercise 7.7:

When \csname is used to define a control sequence for the first time, that control sequence is made equivalent to \relax until it is redefined.

You can test a macro like this:

\documentclass{article}
\usepackage{pgffor}
\usepackage{etoolbox}

\begin{document}

\def\ConditionH{}

\foreach \i in {G, H}{
    \ifcsname Condition\i\endcsname
        \textbackslash Condition\i\ is defined\par
    \else
        \textbackslash Condition\i\ is not defined\par
    \fi
}

\hrule
\foreach \i in {G, H}{% Using etoolbox
    \ifcsdef{Condition\i}{%
        \textbackslash Condition\i\ is defined\par
    }{
        \textbackslash Condition\i\ is not defined\par
    }
}

\end{document}
3
  • 1
    And without eTeX, people often use \expandafter\ifx\csname foo\endcsname\relax ... \fi to test a macro. In LaTeX2e, this is what \@ifundefined do.
    – Leo Liu
    Jun 21, 2011 at 5:55
  • 1
    As I say in my answer, the approach of \@ifundefined works expandably but fills up the hash table, whereas using a group avoids the hash table issue at the cost of not being expandable.
    – Joseph Wright
    Jun 21, 2011 at 7:19
  • 1
    @Joseph: in fact, I realized that even \newcount\i \def\test{\advance\i by 1{\csname foo\the\i\endcsname}\test}\test fills the hash table (and apparently, the control sequence is put in the hash table even when scanned within a \def (or any token list). Jun 23, 2011 at 23:08
7

In addition to Leo's answer, without e-TeX the usual test for an undefined control sequence is

\def\ifundefined#1{%
  \begingroup\expandafter\expandafter\expandafter\endgroup
  \expandafter\ifx\csname #1\endcsname\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}

This works by doing the \csname ... \endcsname inside a group and testing if the meaning of the name we want is equal to \relax. The group means that if the control sequence was undefined then it is only made into \relax within the test, and not globally. The downside is that this test is not expandable. Of course, it cannot tell the difference between an undefined control sequence and one that is deliberately equal to \relax. (The \expandafters here make the \csname happen before the \ifx, which itself takes place before the \endgroup.)

A side note. In the LaTeX2e kernel, \@ifundefined does not use the grouping approach, so that it has an expandable test. That means that each time you use \@ifundefined with a previously-undefined control sequence, it does end up defined as \relax.

1
  • 1
    You can also write this a little shorter by placing the \endgroup commands in the clauses: \begingroup \expandafter\ifx\csname #1\endcsname\relax \endgroup \expandafter\@firstoftwo \else \endgroup \expandafter\@secondoftwo \fi Jun 21, 2011 at 10:03
3

In addition to the other, more relevant answers, I want to mention that your second code block uses \expandafter redundantly. The result of \expandafter\csname Condition\i\endcsname is just \csname Condition\i\endcsname, since the "C" is expanded before \csname. As it turns out, \csname itself continues the expansion and eventually gets \i, but for example something like \expandafter\toks0={\i} will backfire (it expands the 0, not the \i).

It's possible you intended to use \expandafter as an "execute this" command, as though its function were: \expandafter\a\b first expands \b, then expands \a. In fact, all it does is expand \b "in place", without touching \a; if \a does expand, that is because TeX's own "mouth" continues processing the input, expanding as it goes. If \a is not expandable but executable, then it is executed, unless that is inhibited, like in \edef. For example, \edef\macro{\def\a{a}\a} does not define \macro=a, but rather gives an error (assuming \a was not defined already) since it doesn't expand \def and thus, doesn't know that the \a after it is not intended to be expanded. No amount of \expandafters will change this.

If that was not your intention, please take my lecture to be directed at the audience.

1
  • Thanks for pointing that out. Am hoping that one of these days I can fully comprehend \expandafter. Jun 22, 2011 at 0:10

You must log in to answer this question.

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