Search code examples
c++macrosnamespacesc-preprocessor

C++ double colon in macro identifier


I have a logging library for C++ projects that I use. It works in a namespace logger:: and inside that I have all necessary class definitions and pre-defined loggers;

namespace logger {

    class log_level {
        ...
    }

    log_level info;
    log_level debug;
}

Now, why is it logger instead of just log, well because C++ already has a log function, and this would cause a warning message:

warning: built-in function 'log' declared as non-function [-Wbuiltin-declaration-mismatch]

Now this is probably something that could be just ignored, if I don't use log function at all. But even then.. I don't like warnings and errors. So I came to think about a macro that would do the job..

But ever since trying, I have miserably failed as using double colon(:) in macro identifier seems to be a bit more difficult. Possibly impossible as well, but let's ask from the ones who might have better knowledge.

#define log::info logger::info

or

#define PREFIX_LOGGER_NS logger::
#define PREFIX_LOG_NS log::
#define log\:\:info PREFIX_LOGGER_NS info
#define log::debug logger::debug;

all fail.. So, is there a way out of this or is it too much to ask for a descriptive 3 word fake namespace? :)


I eventually decided to use namespace logger instead, even though I don't usually use log - it's better to keep that available and avoid unnecessary warnings.

Thank you for all answers given. They were all good :)


Solution

  • It is impossible because macro substitution happens in translation phase 4, which works on the preprocessing tokens level. The tokens are formed in phase 3 under the rules detailed below. Double colon by itself is a preprocessing token. Quote from cppreference:

    1. The source file is decomposed into comments, sequences of whitespace characters (space, horizontal tab, new-line, vertical tab, and form-feed), and preprocessing tokens, which are the following:

    a) header names such as or "myfile.h"

    b) placeholder tokens produced by preprocessing import and module directives (i.e. import XXX; and module XXX;) (since C++20)

    c) identifiers

    d) preprocessing numbers

    e) character literals, including user-defined character literals(since C++11)

    f) string literals, including user-defined string literals(since C++11)

    g) operators and punctuators (including alternative tokens), such as +, <<=, <%, ##, or and

    h) individual non-whitespace characters that do not fit in any other category

    Double colon falls into category g and cannot be combined with other characters to form a preprocessor token. That means from the perspective of a preprocessor, log::info cannot be treated as a whole at all. This is fundamental to the way how preprocessor works, and there is no possible way around it. If you attempt to somehow escape the colon, then a single colon itself becomes a single token and again cannot be combined with anything else (except maybe forming digraphs listed below). This is just as fundamental as there is no way to treat a= as a single token.

    The complete list of operators and punctuators is given in lex.operators:

    #        ##       %:       %:%: 
    {        }        [        ]        (        )
    <:       :>       <%       %>       ;        :        ... 
    ?       ::       .        .*       ->       ->*      ~
    !        +        -        *        /        %        ^        &        |
    =        +=       -=       *=       /=       %=       ^=       &=       |=
    ==       !=       <        >        <=       >=       <=>      &&       ||
    <<       >>       <<=      >>=      ++       --       ,
    and      or       xor      not      bitand   bitor    compl
    and_eq   or_eq    xor_eq  not_eq
    

    The #define preprocessor directive in addition requires the first token after #define to be a valid identifier.

    For completeness, the requirement for an identifier is the follows,

    The first character of a valid identifier must be one of the following:

    • uppercase Latin letters A-Z
    • lowercase Latin letters a-z
    • underscore
    • any Unicode character with the Unicode property XID_Start

    Any other character of a valid identifier must be one of the following:

    • digits 0-9
    • uppercase Latin letters A-Z
    • lowercase Latin letters a-z
    • underscore
    • any Unicode character with the Unicode property XID_Continue