Due to the character-limit for answers this answer is split in two parts.
This is part 1 of this answer.
Part 2 of this answer can be found here: ⟨https://tex.stackexchange.com/a/539822/118714⟩
When putting your question on a more abstract level, then your question seems to be about iterating on a list of non-delimited macro arguments that contains an arbitrary amount of elements/arguments in order to achieve some sort of foreach-loop.
I can offer some fully expandable solutions where a marker for denoting the end of the list of arguments is not needed as the end of the list is determined by the emptiness of a macro-argument.
With all solutions actually a single macro-argument is processed iteratively which in turn consists of an arbitrary amount of non-delimited arguments.
So instead of
\DoWithEachElementOfArgumentList{⟨Argument 1⟩}{⟨Argument 2⟩}...{⟨Argument n⟩}
the pattern for arguments is:
\DoWithEachElementOfArgumentList{⟨tokens to put before each argument⟩}%
{⟨tokens when no (more) arguments are in list⟩}%
{⟨tokens to put behind each argument⟩}%
{% List of non-delimited macro-arguments:
{⟨Argument 1⟩}%
{⟨Argument 2⟩}%
...
{⟨Argument n⟩}%
}%
This will deliver something like:
⟨tokens to put before each argument⟩{⟨Argument 1⟩}⟨tokens to put behind each argument⟩%
⟨tokens to put before each argument⟩{⟨Argument 2⟩}⟨tokens to put behind each argument⟩%
...
⟨tokens to put before each argument⟩{⟨Argument n⟩}⟨tokens to put behind each argument⟩%
⟨tokens when no (more) arguments are in list⟩
But there is one problem with macro-arguments:
Non-delimited macro-arguments can be wrapped in braces but wrapping them into braces is not necessary as long as they consist only of single tokens.
But there is one exception to that rule:
Space-tokens not wrapped into braces cannot be non-delimited arguments as TeX usually discards them when gathering a non-delimited macro-argument from the token-stream. Thus if a non-delimited macro-argument is to consist of a space-token or is to contain a leading space-token, that argument must be wrapped in braces.
The question arises how you wish space-tokens to be treated.
I suppose
\DoWithEachElementOfArgumentList{\foobar}{}{}{%
{hello}{ }{world}{ }...
}%
should yield:
\foobar{hello}\foobar{ }\foobar{world}\foobar{ }\foobar{...
But what about:
\DoWithEachElementOfArgumentList{\foobar}{}{}{%
{hello} {world} ...
}%
Should that yield
\foobar{hello}\foobar{world}...
or shall it yield
\foobar{hello}\foobar{ }\foobar{world}\foobar{ }...
?
In other words:
Shall space-tokens not wrapped into braces within the list of non-delimited arguments be taken into account in the same way as if they were explicitly wrapped into braces { }? Shall such space-tokens be discarded silently?
Solution 1a—Space-tokens not wrapped into braces get silently discarded:
\documentclass{article}
\usepackage{amsfonts}
\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia:
%%.............................................................................
\newcommand\bracestripexchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%% -- based on \ifempty-macro by Robert R Schneck:
%% Newsgroup: comp.text.tex
%% Subject: Macro puzzle: maximally general \ifempty
%% Google-Groups-URL:
%% <https://groups.google.com/forum/#!topic/comp.text.tex/kuOEIQIrElc>
%%.............................................................................
%% \CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is empty>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is not empty>}%
\long\def\CheckWhetherNull#1{%
\romannumeral0\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
{\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% \ActOnFirstListElement{<tokens to put before each argument>}%
%% {<tokens when no (more) arguments are in list>}%
%% {<tokens to put behind each argument>}%
%% {{<e_k>}{<e_(k+1)>}..{<e_n>}}%
%% yields either:
%% <tokens when no (more) arguments are in list>
%% or:
%% <tokens to put before each argument>{<e_k>}<tokens to put behind each
%% argument>{{<e_(k+1)>}..{<e_n>}}
%%
%% ( The "list" in question is {<e_k>}{<e_(k+1)>}..{<e_n>} )
%%.............................................................................
\@ifdefinable\KeepFirstTillSelDOM{%
\long\def\KeepFirstTillSelDOM#1#2\SelDOM{{#1}}%
}%
\newcommand\ActOnFirstListElement[4]{%
\expandafter\CheckWhetherNull\expandafter{\@secondoftwo#4.{}}%
{#2}%
{%
\expandafter\expandafter
\expandafter \ExtractFirstListElementLoop
\expandafter\bracestripexchange
\expandafter{%
\expandafter{%
\@firstoftwo{}#4}}{{#4\SelDOM}{#1}{#3}}%
}%
}%
\newcommand\ExtractFirstListElementLoop[3]{%
\expandafter\CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
{#2#1#3}%
{%
\expandafter\ExtractFirstListElementLoop
\expandafter{%
\KeepFirstTillSelDOM#1}{#2}{#3}%
}%
}%
%%---------------------------------------------------------------
%% Expandable Loop:
%% \DoWithEachElementOfArgumentList{<tokens to put before each argument>}%
%% {<tokens when no (more) arguments are in list>}%
%% {<tokens to put behind each argument>}%
%% {{<e_k>}{<e_(k+1)>}..{<e_n>}}
%%
%% If iteration is done/if list is empty: <tokens when no (more) arguments are in list>
%% Else:
%% <tokens to put before each argument>{<e_k>}<tokens to put behind each argument>%
%% \DoWithEachElementOfArgumentList{<tokens to put before each argument>}%
%% {<tokens when no (more) arguments are in list>}%
%% {<tokens to put behind each argument>}%
%% {{<e_(k+1)>}..{<e_n>}}%
%%...............................................................
\newcommand\DoWithEachElementOfArgumentList[3]{%
\ActOnFirstListElement{#1}{#2}{#3\DoWithEachElementOfArgumentList{#1}{#2}{#3}}%
}
\makeatother
%%-----------------------------------------------------------------------------
%% Test the for-loop:
%%.............................................................................
\newcommand{\magic}[1]{%
\expandafter\newcommand\csname b#1\endcsname{\mathbb{#1}}%
\expandafter\newcommand\csname c#1\endcsname{\mathcal{#1}}%
\expandafter\newcommand\csname f#1\endcsname{\mathfrak{#1}}%
}%
\DoWithEachElementOfArgumentList{\magic}{}{}{%
ABCDEFGHIJKLMNOPQRSTUVWXYZ%
}%
\begin{document}
\ttfamily\selectfont
\noindent $\bX \cB \fH$
\vfill
\newcommand\callmacros[1]{%
\hbox{%
\hbox{\expandafter\string\csname b#1\endcsname: $\csname b#1\endcsname$} %
\hbox{\expandafter\string\csname c#1\endcsname: $\csname c#1\endcsname$} %
\hbox{\expandafter\string\csname f#1\endcsname: $\csname f#1\endcsname$}%
}%
}%
\DoWithEachElementOfArgumentList{\callmacros}{\hbox{Done.}}{}{ABCDEFGHIJKLMNOPQRSTUVWXYZ}%
\vfill
\newcommand\TokensToPutBeforeArg[1]{%
\string\TokensToPutBeforeArg\string{#1\string}%
}%
\DoWithEachElementOfArgumentList{\par\noindent\TokensToPutBeforeArg}%
{\par\noindent Done.}%
{(TokensToPutBehindArg)}%
{ %<-Space-Token!
{Non-Space-Element01}%<-No Space-Token
{Non-Space-Element02} %<- Space-Token
{Non-Space-Element03}%<-No Space-Token
{Non-Space-Element04} %<- Space-Token
{Non-Space-Element05}%<-No Space-Token
{Non-Space-Element06}%<-No Space-Token
{Non-Space-Element07}%<-No Space-Token
{Non-Space-Element08}%<-No Space-Token
{Non-Space-Element09}%<-No Space-Token
{Non-Space-Element10}%<-No Space-Token
{Non-Space-Element11}%<-No Space-Token
{Non-Space-Element12} %<- Space-Token
}%
\vfill
\end{document}

Solution 1b—Space-tokens not wrapped into braces are taken into account as if they were wrapped into braces:
\documentclass{article}
\usepackage{amsfonts}
\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia:
%%.............................................................................
\newcommand\bracestripexchange[2]{#2#1}%
\@ifdefinable\removespace{\@firstoftwo{\def\removespace}{} {}}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%% -- based on \ifempty-macro by Robert R Schneck:
%% Newsgroup: comp.text.tex
%% Subject: Macro puzzle: maximally general \ifempty
%% Google-Groups-URL:
%% <https://groups.google.com/forum/#!topic/comp.text.tex/kuOEIQIrElc>
%%.............................................................................
%% \CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is empty>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is not empty>}%
\newcommand\CheckWhetherNull[1]{%
\romannumeral0\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
{\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
\newcommand\CheckWhetherLeadingSpace[1]{%
\CheckWhetherNull{#1}{\@secondoftwo}{%
\expandafter\@secondoftwo\string{\CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\@ifdefinable\CheckWhetherLeadingSpaceB{%
\long\def\CheckWhetherLeadingSpaceB#1 {%
\expandafter\CheckWhetherNull\expandafter{\@secondoftwo#1{}}%
{\expandafter\expandafter\expandafter\@firstoftwo}%
{\expandafter\expandafter\expandafter\@secondoftwo}%
\expandafter\@secondoftwo\expandafter{\string}%
}%
}%
%%-----------------------------------------------------------------------------
%% \ActOnFirstListElement{<tokens to put before each argument>}%
%% {<tokens when no (more) arguments are in list>}%
%% {<tokens to put behind each argument>}%
%% {{<e_k>}{<e_(k+1)>}..{<e_n>}}%
%% yields either: <tokens when no (more) arguments are in list>
%% or: <tokens to put before each argument>{<e_k>}<tokens to put behind each argument>{{<e_(k+1)>}..{<e_n>}}
%%
%% ( The "list" in question is {<e_k>}{<e_(k+1)>}..{<e_n>} )
%%.............................................................................
\@ifdefinable\KeepFirstTillSelDOM{%
\long\def\KeepFirstTillSelDOM#1#2\SelDOM{{#1}}%
}%
\newcommand\ActOnFirstListElement[4]{%
\CheckWhetherNull{#4}%
{#2}%
{%
\CheckWhetherLeadingSpace{#4}{%
\expandafter\bracestripexchange
\expandafter{%
\expandafter{%
\removespace#4}}{#1{ }#3}%
}{%
\expandafter\expandafter
\expandafter \ExtractFirstListElementLoop
\expandafter\bracestripexchange
\expandafter{%
\expandafter{%
\@firstoftwo{}#4}}{{#4\SelDOM}{#1}{#3}}%
}%
}%
}%
\newcommand\ExtractFirstListElementLoop[3]{%
\expandafter\CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
{#2#1#3}%
{%
\expandafter\ExtractFirstListElementLoop
\expandafter{%
\KeepFirstTillSelDOM#1}{#2}{#3}%
}%
}%
%%---------------------------------------------------------------
%% Expandable Loop:
%% \DoWithEachElementOfArgumentList{<tokens to put before each argument>}%
%% {<tokens when no (more) arguments are in list>}%
%% {<tokens to put behind each argument>}%
%% {{<e_k>}{<e_(k+1)>}..{<e_n>}}
%%
%% If list is empty: <tokens when no (more) arguments are in list>
%% Else:
%% <tokens to put before each argument>{<e_k>}<preset>%
%% \DoWithEachElementOfArgumentList{<tokens to put before each argument>}%
%% {<tokens when no (more) arguments are in list>}%
%% {<tokens to put behind each argument>}
%% {{<e_(k+1)>}..{<e_n>}}
%%...............................................................
\newcommand\DoWithEachElementOfArgumentList[3]{%
\ActOnFirstListElement{#1}{#2}{#3\DoWithEachElementOfArgumentList{#1}{#2}{#3}}%
}
\makeatother
%%-----------------------------------------------------------------------------
%% Test the for-loop:
%%.............................................................................
\newcommand{\magic}[1]{%
\expandafter\newcommand\csname b#1\endcsname{\mathbb{#1}}%
\expandafter\newcommand\csname c#1\endcsname{\mathcal{#1}}%
\expandafter\newcommand\csname f#1\endcsname{\mathfrak{#1}}%
}%
\DoWithEachElementOfArgumentList{\magic}{}{}{%
ABCDEFGHIJKLMNOPQRSTUVWXYZ%
}%
\begin{document}
\ttfamily\selectfont
\noindent $\bX \cB \fH$
\vfill
\newcommand\callmacros[1]{%
\hbox{%
\hbox{\expandafter\string\csname b#1\endcsname: $\csname b#1\endcsname$} %
\hbox{\expandafter\string\csname c#1\endcsname: $\csname c#1\endcsname$} %
\hbox{\expandafter\string\csname f#1\endcsname: $\csname f#1\endcsname$}%
}%
}%
\DoWithEachElementOfArgumentList{\callmacros}{\hbox{Done.}}{}{ABCDEFGHIJKLMNOPQRSTUVWXYZ}%
\vfill
\newcommand\TokensToPutBeforeArg[1]{%
\string\TokensToPutBeforeArg\string{#1\string}%
}%
\DoWithEachElementOfArgumentList{\par\noindent\TokensToPutBeforeArg}%
{\par\noindent Done.}%
{(TokensToPutBehindArg)}%
{ %<-Space-Token!
{Non-Space-Element01}%<-No Space-Token
{Non-Space-Element02} %<- Space-Token
{Non-Space-Element03}%<-No Space-Token
{Non-Space-Element04} %<- Space-Token
{Non-Space-Element05}%<-No Space-Token
{Non-Space-Element06}%<-No Space-Token
{Non-Space-Element07}%<-No Space-Token
{Non-Space-Element08}%<-No Space-Token
{Non-Space-Element09}%<-No Space-Token
{Non-Space-Element10}%<-No Space-Token
{Non-Space-Element11}%<-No Space-Token
{Non-Space-Element12} %<- Space-Token
}%
\vfill
\end{document}

Due to the character-limit for answers this answer is split in two parts.
This is part 1 of this answer.
Part 2 of this answer can be found here: ⟨https://tex.stackexchange.com/a/539822/118714⟩
\newcommands that warn you if you overwrite an existing macro. Of course, I also removed stray spaces. – Apr 20 '20 at 23:36