Another option would be to use \@bsphack
and \@esphack
, like this:
\documentclass{article}
\makeatletter %% <- make @ usable in macro names
\newcommand{\comm}[1]{\@bsphack\@esphack}
\makeatother %% <- change @ back
\begin{document}
A Test. \comm{test} This works.
A Test.\comm{test} This works.
A Test. \comm{test}This works.
A Test\comm{test}. This works.
\end{document}
This produces a space of the right length, also if \comm
actually does something. This is how for instance \label
(which creates a label that can be cross-referenced), \index
(which creates an index entry) and \marginpar
(which puts text in the margin) work.
The way to use \@bsphack
and \@esphack
in general would be:
\newcommand{\comm}[1]{\@bsphack<do things here>\@esphack}
A little explanation:
\@bsphack
and \@esphack
do the following (more or less):
\@bsphack
stores the values of \lastskip
and \spacefactor
. The first is the length of the preceding space (if applicable, otherwise it is 0), and the second is a number that affects the length of a space at this spot. (E.g., spaces following periods are slightly longer and can be stretched to become a lot longer if necessary.)
\@esphack
restores \spacefactor
and inserts \ignorespaces
(which does what it says) if \lastskip
was positive when \@bsphack
was called.
There are (at least) two limitations: multiple consecutive \@bsphack
/\@sphack
s don't work, and using this at the end of a paragraph can sometimes cause the last word of this paragraph to be moved to the next line.
I actually use a slightly modified version myself that does allow you to use multiple of these in a row and doesn't have the end-of-paragraph problem. It removes the preceding space and then reinserts it afterwards.
\documentclass{article}
\newcommand{\comm}[1]{\mybsphack\myesphack}
%% This also works if \comm does do something:
% \newcommand{\comm}[1]{\mybsphack{\marginpar{#1}}\myesphack}
\makeatletter %% <- make @ usable in macro names
\newskip\my@savsk %% <- holds a skip
\newcount\my@savsf %% <- holds space factor
\newcount\my@savpn %% <- holds penalty (for line breaking)
\newcommand*{\mybsphack}{% %% <- this % is relevant, see the comments
\relax\ifhmode %% <- if in horizontal mode
\my@savsk\lastskip %% <- store \lastskip
\@savsf\spacefactor %% <- store \spacefactor
\my@savpn\lastpenalty %% <- store \lastpenalty
\unskip %% <- remove the last skip
\fi
}
\newcommand*{\myesphack}{% %% <- this % is relevant, see the comments
\relax\ifhmode %% <- if in horizontal mode
\penalty\my@savpn %% <- restore the penalty
\spacefactor\@savsf %% <- restore the space factor
\ifdim\my@savsk>\z@ %% <- if the removed skip was positive
\hskip\my@savsk %% <- then reinsert it
\ignorespaces %% <- and ignore subsequent spaces
\fi
\fi
}
\makeatother %% <- change @ back
\begin{document}
A Test. \comm{test}\comm{test}This works.
A Test.\comm{test}\comm{test} This works.
A Test. \comm{test}\comm{test} This works.
A Test. \comm{test} \comm{test}This works.
A Test.\comm{test} \comm{test} This works.
A Test. \comm{test} \comm{test} This works.
A Test\comm{test}\comm{test}. This works.
\end{document}

Edit:
If \comm
is supposed to truly do nothing, then the following would suffice:
\newcommand{\comm}[1]{\ifdim\lastskip>0pt\ignorespaces\fi}
Edit:
Improved my code: it now takes into account penalties and stores \lastskip
in a skip register rather than the default dimension register (oops).