Behaviour changes in math mode when macro is defined dynamically

Why is my third O bigger than the first two ones?

\documentclass{report}
\usepackage[english]{babel}
\usepackage{pgffor}
\usepackage{amsmath}

% defining a plain macro
\def\macro{\text{O}}

% defining it dynamically
\foreach \mname/\cmd in {%
dynmacro/\text{O},%
} {%
\expandafter\xdef\csname\mname\endcsname{\cmd}%
}

\begin{document}

$$2^{\text{O}} \times % okay 2^{\macro} \times % okay 2^{\dynmacro} % this one yields a weird result$$

\end{document}


How do I correct my dynamic macro?

If you ask \show\dynmacro after \begin{document} you’ll be answered

> \dynmacro=macro:
->\protect \unhbox \voidb@x \hbox {O}.


which is nothing you’d like, isn't it? The problem is \xdef. You're even lucky that something meaningful happens. With \textbf instead of \text you’d get several errors. You need just one step of expansion.

\documentclass{report}
\usepackage[english]{babel}
\usepackage{pgffor}
\usepackage{amsmath}

% defining a plain macro
\def\macro{\text{O}}

% defining it dynamically
\foreach \mname/\cmd in {%
dynmacro/\text{O},%
} {%
\expandafter\gdef\csname\mname\expandafter\endcsname\expandafter{\cmd}%
}

\begin{document}

\show\dynmacro

$$2^{\text{O}} \times % okay 2^{\macro} \times % okay 2^{\dynmacro} % this one yields a weird result$$

\end{document}


The console now will report

> \dynmacro=macro:
->\text {O}.


Important note: \text is not the best for this, it should probably be \mathrm.

A different implementation: \dyndef wants three arguments

1. the input form (optional, default #1=#2)
2. the definition to perform
3. the list of things to define

Here are a couple of examples.

\documentclass{report}
\usepackage{amsmath}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\dyndef}{O{##1=##2}mm}
{
\cs_set:Npn \__iagolito_dyndef_aux:w #1 \q_stop { #2 }
\clist_map_function:nN { #3 } \iagolito_dyndef:n
}
\cs_new:Nn \iagolito_dyndef:n { \__iagolito_dyndef_aux:w #1 \q_stop }
\ExplSyntaxOff

% defining it dynamically
\dyndef{\expandafter\newcommand\csname#1\endcsname{#2}}{
dynmacro=\text{O},
dynmacroA=\mathbf{0},
}

\dyndef[#1/#2/#3]{\newcommand#1{#2-#3}}{
\foo/A/B,
}

\begin{document}

$$2^{\text{O}} \times 2^{\dynmacro} + 2^{\dynmacroA}+(\foo)$$

\end{document}


As you see, the first example uses the default dynmacro=\text{O}; for the second example, the specification of the argument is #1/#2/#3; the same placeholders are used in the first mandatory argument.

• Beat me by a few minutes, I see. I will delete my duplicative answer. – Steven B. Segletes Apr 19 '18 at 13:09
• Next question would be why \text? – daleif Apr 19 '18 at 13:10
• @daleif I added a note – egreg Apr 19 '18 at 13:20
• I would propose the first argument to be optional with #1=#2 as a default which seems more usual. The fact that he used / in the question is because he was using \foreach. – Manuel Apr 19 '18 at 13:40
• @Manuel I concur. – egreg Apr 19 '18 at 13:44

The main problem is that \xdef completely expands its argument, and this adds an \unhbox\voidb@x to your \dynmacro. Using etoolbox's \expandonce you can make the \cmd expand once only (and using \csxdef to make it simpler):

\documentclass{report}
\usepackage[english]{babel}
\usepackage{pgffor}
\usepackage{amsmath}
\usepackage{etoolbox}

% defining a plain macro
\def\macro{\text{O}}

% defining it dynamically
\foreach \mname/\cmd in {%
dynmacro/\text{O},%
} {%
\csxdef{\mname}{\expandonce\cmd}%
}

\begin{document}

$$2^{\text{O}} \times % okay 2^{\macro} \times % okay 2^{\dynmacro} % okay :)$$

\end{document}


You can use a loop not encapsulating items in macros

\documentclass{report}
\usepackage[english]{babel}
\usepackage{pgffor}
\usepackage{amsmath}
\usepackage{xinttools}

% defining a plain macro
\def\macro{\text{O}}

\makeatletter % for LaTeX's \@namedef
% defining it dynamically
\xintForpair #1#2 in {%
(dynmacro, \text{O})%
% add more comma-separated "pairs" if needed
% (no ending comma, though, only to separate pairs)
}\do
{%
\@namedef{#1}{#2}%
}
\makeatother

\begin{document}

$$2^{\text{O}} \times % okay 2^{\macro} \times % okay 2^{\dynmacro} % this one is also okay$$

% \showoutput
\end{document}