Inspired by wipet's answer I suggest a \let-\afterassignment-loop:
\def\process#1{\afterassignment\doA\let\next= #1\process\process}
\def\doA{%
\ifx\next\process\else
\message{\meaning\next}%
\afterassignment\doA
\fi
\let\next= %
}
\process{ ab}
\message{done}
\end
(It is assumed that the argument of \process does not contain tokens whose meaning equals the meaning of \process.)
Console output is:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdftex)
restricted \write18 enabled.
entering extended mode
(./test.tex blank space the letter a the letter b done )
No pages of output.
Transcript written on test.log.
To your "related question":
Instead of
\def\defmyspace#1{\futurelet\myspace\relax#1}
\defmyspace{ }
\show\myspace
take into account that \let ... = ... consumes exactly one optional space behind = and do something like:
\def\myspace#1{#1}
\myspace{\let\myspace= } %
\show\myspace
As long as the \lccode of the space-character and the \lccode of the =-character do not indicate that \lowercase affects these characters you can also do:
\lowercase{\let\myspace= } %
\show\myspace
In LaTeX 2ε \@sptoken is available.
As Wipet already provided an excellent answer exhibiting the usage of \let, \futurelet and \afterassignment I decided to provide a totally different approach to the matter—one which probably is suitable if you actually wish to "look" at things argumentwise.
Depending on what you intend to do exactly, you can probably tail-recursively
iterate without assignments/without \futurelet/\let:
%%-----------------------------------------------------------------------------
%% Paraphernalia:
%%.............................................................................
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\exchange#1#2{#2#1}%
\chardef\stopromannumeral=`\^^00%
\secondoftwo{}{\long\def\removespace} {}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \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>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\long\def\CheckWhetherNull#1{%
\romannumeral\expandafter\secondoftwo\string{\expandafter
\secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\stopromannumeral\secondoftwo}%
{\expandafter\stopromannumeral\firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is an explicit
%% catcode-1-character-token
%%.............................................................................
%% \CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has leading
%% explicit catcode-1-character-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not have a leading
%% explicit catcode-1-character-token>}%
\long\def\CheckWhetherBrace#1{%
\romannumeral\expandafter\secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\stopromannumeral\firstoftwo}%
{\expandafter\stopromannumeral\secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case
%% <argument which is to be checked>
%% does have a leading explicit space-
%% token>}%
%% {<Tokens to be delivered in case
%% <argument which is to be checked>
%% does not have a leading explicit
%% space-token>}%
\long\def\CheckWhetherLeadingExplicitSpace#1{%
\romannumeral\CheckWhetherNull{#1}%
{\expandafter\stopromannumeral\secondoftwo}%
{%
% Let's nest things into \firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\stopromannumeral
\romannumeral\expandafter\secondoftwo
\string{\CheckWhetherLeadingExplicitSpaceB.#1 }{}%
}{}%
}%
}%
\long\def\CheckWhetherLeadingExplicitSpaceB#1 {%
\expandafter\CheckWhetherNull\expandafter{\firstoftwo{}#1}%
{\exchange{\firstoftwo}}{\exchange{\secondoftwo}}%
{\expandafter\expandafter\expandafter\stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \romannumeral\ExtractFirstArgLoop{ABCDE\SelDOm} yields A
%%
%% \romannumeral\ExtractFirstArgLoop{{AB}CDE\SelDOm} yields AB
%%
%% \romannumeral\ExtractFirstArgLoop{\SelDOm ABCDE\SelDOm} yields \SelDOm
%%.............................................................................
\long\def\RemoveTillSelDOm#1#2\SelDOm{{#1}}%
\long\def\ExtractFirstArgLoop#1{%
\expandafter\CheckWhetherNull\expandafter{\firstoftwo{}#1}%
{\expandafter\stopromannumeral\secondoftwo{}#1}%
{\expandafter\ExtractFirstArgLoop\expandafter{\RemoveTillSelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%%
\long\def\process#1{%
\CheckWhetherNull{#1}{}{%
\CheckWhetherLeadingExplicitSpace{#1}{%
The first token of {\tt\string\process} is an explicit space token.\hfill\break
\expandafter\process\expandafter{\removespace#1}%
}{%
\CheckWhetherBrace{#1}{%
The first token of {\tt\string\process} is an opening brace.\hfill\break
\expandafter\process\expandafter{\romannumeral\ExtractFirstArgLoop{#1\SelDOm}}%
The first token of {\tt\string\process} is a closing brace.\hfill\break
}{%
The first token of {\tt\string\process} is: {\tt\expandafter\string\romannumeral\ExtractFirstArgLoop{#1\SelDOm}}\hfill\break
}%
\expandafter\process\expandafter{\firstoftwo{}#1}%
}%
}%
}%
\noindent{\tt\string\noindent}\hfill\break
{\tt\string\process\string{ ab\string{cd\string}e f \string\TeX\string}}
\bigskip
\noindent
\process{ ab{cd}e f \TeX}%
\bye

From a comment of yours I learned that the issue is about stringification of an argument in LaTeX 2ε.
Be aware that the approach above is not suitable, e.g., for proper stringification of arguments because matching curly braces are consumed without actually examining the closing brace.
I can offer a stringification-routine which examines opening- and closing braces. (Instead of {1/}2 there could be something like X1/Y2 or even worse ␣1/␣2 ...)
%% Copyright (C) 2019, 2020 by Ulrich Diez (eu_angelion@web.de)
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public Licence (LPPL), either
%% version 1.3 of this license or (at your option) any later
%% version. (The latest version of this license is in:
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included
%% documentation nor for any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
\errorcontextlines=10000
\documentclass{article}
\makeatletter
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo,
%% \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%% \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%% \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=\^^00}% %%----------------------------------------------------------------------------- %% Check whether argument is empty: %%............................................................................. %% \UD@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>}% %% %% The gist of this macro comes from Robert R. Schneck's \ifempty-macro: %% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J> \newcommand\UD@CheckWhetherNull[1]{% \romannumeral\expandafter\UD@secondoftwo\string{\expandafter \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{% \expandafter\UD@stopromannumeral\UD@firstoftwo}% }% %%----------------------------------------------------------------------------- %% Check whether argument's first token is a catcode-1-character %%............................................................................. %% \UD@CheckWhetherBrace{<Argument which is to be checked>}% %% {<Tokens to be delivered in case that argument %% which is to be checked has a leading %% explicit catcode-1-character-token>}% %% {<Tokens to be delivered in case that argument %% which is to be checked does not have a %% leading explicit catcode-1-character-token>}% \newcommand\UD@CheckWhetherBrace[1]{% \romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{% \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{% \expandafter\UD@stopromannumeral\UD@secondoftwo}% }% %%----------------------------------------------------------------------------- %% Check whether brace-balanced argument starts with a space-token %%............................................................................. %% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}% %% {<Tokens to be delivered in case <argument %% which is to be checked> does have a %% leading explicit space-token>}% %% {<Tokens to be delivered in case <argument %% which is to be checked> does not have a %% a leading explicit space-token>}% \newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{% \romannumeral\UD@CheckWhetherNull{#1}% {\expandafter\UD@stopromannumeral\UD@secondoftwo}% {% % Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces % and thus do not disturb when the test is carried out within \halign/\valign: \expandafter\UD@firstoftwo\expandafter{% \expandafter\expandafter\expandafter\UD@stopromannumeral \romannumeral\expandafter\UD@secondoftwo \string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}% }{}% }% }% \@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{% \long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {% \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}% {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}% {\expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter\expandafter\expandafter}% \expandafter\UD@secondoftwo\expandafter{\string}% }% }% %%----------------------------------------------------------------------------- %% Extract first inner undelimited argument: %% %% \UD@ExtractFirstArg{ABCDE} yields {A} %% %% \UD@ExtractFirstArg{{AB}CDE} yields {AB} %%............................................................................. \@ifdefinable\UD@RemoveTillUD@SelDOm{% \long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}% }% \newcommand\UD@ExtractFirstArg[1]{% \romannumeral\UD@ExtractFirstArgLoop{#1\UD@SelDOm}% }% \newcommand\UD@ExtractFirstArgLoop[1]{% \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}% {\UD@stopromannumeral#1}% {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}% }% %%----------------------------------------------------------------------------- %% In case an argument's first token is an opening brace, stringify that and %% add another opening brace before that and remove everything behind the %% matching closing brace: %% \UD@StringifyOpeningBrace{{Foo}bar} yields {{Foo} whereby the second %% opening brace is stringified: %%............................................................................. \newcommand\UD@StringifyOpeningBrace[1]{% \romannumeral \expandafter\UD@ExtractFirstArgLoop\expandafter{% \romannumeral\expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter\expandafter \expandafter {% \expandafter\UD@firstoftwo \expandafter{% \expandafter}% \romannumeral\expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter\string \expandafter}% \string#1% \UD@SelDOm}% }% %%----------------------------------------------------------------------------- %% In case an argument's first token is an opening brace, remove everything till %% finding the corresponding closing brace. Then stringify that closing brace: %% \UD@StringifyClosingBrace{{Foo}bar} yields: {}bar} whereby the first closing %% brace is stringified: %%............................................................................. \newcommand\UD@StringifyClosingBrace[1]{% \romannumeral\expandafter\expandafter\expandafter \UD@StringifyClosingBraceloop \UD@ExtractFirstArg{#1}{#1}% }% \newcommand\UD@CheckWhetherStringifiedOpenBraceIsSpace[1]{% %% This can happen when character 32 (space) has catcode 1... \expandafter\UD@CheckWhetherLeadingExplicitSpace\expandafter{% \romannumeral\expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter\UD@secondoftwo \expandafter{% \expandafter}% \expandafter{% \romannumeral\expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter\UD@firstoftwo \expandafter{% \expandafter}% \romannumeral\expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter\string \expandafter}% \string#1% }% }% \newcommand\UD@TerminateStringifyClosingBraceloop[2]{% \expandafter\expandafter\expandafter\UD@stopromannumeral% \expandafter\expandafter \expandafter{% \expandafter\string \romannumeral\expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter#1% \string#2% }% }% \newcommand\UD@StringifyClosingBraceloopRemoveElement[4]{% \expandafter\UD@PassFirstToSecond\expandafter{\expandafter {\romannumeral\expandafter\UD@secondoftwo\string}{}% \UD@CheckWhetherStringifiedOpenBraceIsSpace{#4}{% \UD@Exchange{\UD@removespace}% }{% \UD@Exchange{\UD@firstoftwo\expandafter{\expandafter}}% }{% \expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter#1% \romannumeral\expandafter\expandafter\expandafter\UD@stopromannumeral \expandafter }% \string#4% }{\expandafter\UD@StringifyClosingBraceloop\expandafter{#2#3}}% }% \newcommand\UD@StringifyClosingBraceloop[2]{% \UD@CheckWhetherNull{#1}{% \UD@CheckWhetherStringifiedOpenBraceIsSpace{#2}{% \UD@TerminateStringifyClosingBraceloop{\UD@removespace}% }{% \UD@TerminateStringifyClosingBraceloop{\UD@firstoftwo\expandafter{\expandafter}}% }% {#2}% }{% \UD@CheckWhetherLeadingExplicitSpace{#1}{% \UD@StringifyClosingBraceloopRemoveElement {\UD@removespace}{\UD@removespace}% }{% \UD@StringifyClosingBraceloopRemoveElement {\UD@firstoftwo\expandafter{\expandafter}}{\UD@firstoftwo{}}% }% {#1}{#2}% }% }% %%----------------------------------------------------------------------------- %% Apply <action> to the stringification of each token of the argument: %% %% \StringifyNAct{<action>}{<token 1><token 2>...<token n>} %% %% yields: <action>{<stringification of token 1>}% %% <action>{<stringification of token 2>}% %% ... %% <action>{<stringification of token n>}% %% %% whereby "stringification of token" means the result of applying \string %% to the token in question. %% Due to \romannumeral-expansion the result is delivered after two %% \expandafter-chains. %% If you leave <action> empty, you can apply a loop on the list formed by %% {<stringification of token 1>}% %% {<stringification of token 2>}% %% ... %% {<stringification of token n>}% %%............................................................................. \newcommand\StringifyNAct{% \romannumeral\StringifyNActLoop{}% }% %%............................................................................. %% \StringifyNActLoop{{<stringification of token 1>}...{<stringification of token k-1>}}% %% {<action>}% %% {<token k>...<token n>} %%............................................................................. \newcommand\StringifyNActLoop[3]{% \UD@CheckWhetherNull{#3}{% \UD@stopromannumeral#1% }{% \UD@CheckWhetherBrace{#3}{% \expandafter\expandafter\expandafter\UD@Exchange \expandafter\expandafter\expandafter{% \UD@StringifyClosingBrace{#3}% }{% \expandafter\StringifyNActLoop\expandafter{% \romannumeral \expandafter\expandafter\expandafter\UD@Exchange \expandafter\expandafter\expandafter{\UD@StringifyOpeningBrace{#3}}{\StringifyNActLoop{#1}{#2}}% }{#2}% }% }{% \UD@CheckWhetherLeadingExplicitSpace{#3}{% \expandafter\UD@PassFirstToSecond\expandafter{\UD@removespace#3}{% \StringifyNActLoop{#1#2{ }}{#2}% }% }{% \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{% \expandafter\StringifyNActLoop\expandafter{% \romannumeral% \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\UD@PassFirstToSecond \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter{% \expandafter\expandafter\expandafter\string \expandafter\UD@Exchange \romannumeral\UD@ExtractFirstArgLoop{#3\UD@SelDOm}{}% }{\UD@stopromannumeral#1#2}% }% {#2}% }% }% }% }% }% %%............................................................................. %% Now a routine which you can apply as <action> within \StringifyNAct: %%............................................................................. \newcommand\printstringifiedtoken[1]{% A token was stringified as \UD@CheckWhetherLeadingExplicitSpace{#1}{% \fbox{\texttt{\char\ \strut}} (explicit space token\strut)%
}{%
\fbox{\texttt{#1\strut}}%
}
.\
}%
%%.............................................................................
%% Now a routine which you can apply when prefering iterating on the result
%% of \StringifyNAct
%%.............................................................................
\newcommand\printstringifiedtokenloop[1]{%
\ifx\relax#1\expandafter@gobble\else\expandafter@firstofone\fi
{\printstringifiedtoken{#1}\printstringifiedtokenloop}%
}%
\makeatother
\pagestyle{empty}
\begin{document}
\vspace*{-4cm}\enlargethispage{4cm}
\begin{verbatim}
\noindent
\StringifyNAct{\printstringifiedtoken}{%
\textbf{\csname @firstofone\endcsname{\LaTeX} is funny.}
}
\end{verbatim}
yields:\bigskip
\noindent
\StringifyNAct{\printstringifiedtoken}{%
\textbf{\csname @firstofone\endcsname{\LaTeX} is funny.}
}
(The last explicit space token is due to the \verb|\endlinechar|-thingie
while the state of \LaTeX's reading-apparatus is in state M (middle of line)
after a curly closing brace. It also is in that state after an opening
curly brace.)
\newpage
\vspace*{-4cm}\enlargethispage{4cm}
\begin{verbatim}
\noindent
\expandafter\expandafter
\expandafter\printstringifiedtokenloop
\StringifyNAct{}{%
\textbf{\csname @firstofone\endcsname{\LaTeX} is funny.}
}%
\relax
\end{verbatim}
yields:\bigskip
\noindent
\expandafter\expandafter
\expandafter\printstringifiedtokenloop
\StringifyNAct{}{%
\textbf{\csname @firstofone\endcsname{\LaTeX} is funny.}
}%
\relax
(The last explicit space token is due to the \verb|\endlinechar|-thingie
while the state of \LaTeX's reading-apparatus is in state M (middle of line)
after a curly closing brace. It also is in that state after an opening
curly brace.)
\end{document}


\@ifnextcharsimilarly the latex definition of\@sptokenanswers your related question I think. – David Carlisle Dec 13 '20 at 17:57{ ab}.\@ifnextcharhides the space and lets me access onlyaandb. The code in my question gives me access to the space, but I can't find a way to proceed (=skip the space) in a way such that I can futureleta. Yes,\@sptokenis what I was looking for. It took me a bit to understand why it is defined using\:instead of\somemacro; clever. – gernot Dec 13 '20 at 20:28