1

I am trying to define a macro that behaves differently when used in a function context vs a class body or namespace. The purpose of this is to selectively include BOOST_LOG_NAMED_SCOPE (or achieve equivalent behavior) in the LOG_CONTEXT macro referenced here.

I have tried two approaches:

  1. Using BOOST_PP_IF along with a preprocessor function to test whether e.g. __func__ is non-empty.

    The output of g++ -E on a basic source file still contains the literal text __func__ in both global and function scope so that probably rules out any approach targeting the preprocessing phase.

  2. Using something like sizeof(__func__) to select a specialized template that implements behavior similar to BOOST_LOG_NAMED_SCOPE. I can re-use the boost::log::attributes::named_scope::sentry object from boost log, but I'm stuck trying to figure out how to instantiate it conditionally in a function context, or at least in a way that works everywhere. The following construction seems to work fine in class definitions and functions, but fails with a "multiple definition" error when linking together multiple translation units that include a header with a LOG_CONTEXT at global or namespace scope:

    #include <boost/log/attributes/named_scope.hpp>
    
    #include <type_traits>
    
    namespace logging {
    
    namespace attrs = boost::log::attributes;
    
    namespace detail  {
    
    // Default no-op on construction when not in a function.
    template<typename ScopeTraits, bool InFunction>
    class named_scope_helper
    {};
    
    // Specialization when in a function.
    template<typename ScopeTraits>
    class named_scope_helper<ScopeTraits, true> :
        public attrs::named_scope::sentry
    {
     public:
        named_scope_helper() BOOST_NOEXCEPT
        : sentry(
            ScopeTraits::scope_name(),
            ScopeTraits::filename(),
            ScopeTraits::lineno() )
        {}
    };
    
    template<size_t N>
    class not_1 : public std::true_type
    {};
    
    template<>
    class not_1<1> : public std::false_type
    {};
    
    #define LOGGING_LOG_IN_FUNCTION \
        ::logging::detail::not_1<sizeof(__func__)>::value
    
    } // namespace detail
    } // namespace logging
    
    // scope_name_t/filename_t are required since attrs::named_scope::sentry
    // requires string literals.
    #define LOG_CONTEXT( name_ ) \
        struct __logging_log_scope_traits__ \
        { \
            using scope_name_t = const char (&)[sizeof(name_)]; \
            static scope_name_t scope_name() \
            { return name_; } \
            using filename_t = const char (&)[sizeof(__FILE__)]; \
            static filename_t filename() \
            { return __FILE__; } \
            static size_t lineno() \
            { return __LINE__; } \
        }; \
        ::logging::detail::named_scope_helper< \
            __logging_log_scope_traits__, LOGGING_LOG_IN_FUNCTION> \
                BOOST_LOG_UNIQUE_IDENTIFIER_NAME(_log_named_scope_sentry_);
    

    My normal workaround for this would be to use static in the declaration of the _log_named_scope_sentry_, but that defeats the purpose.

I could add another macro that is strictly for non-execution contexts, but wanted to investigate this approach first since it would be an interesting trick to have. How can I proceed on one of the two approaches I've started above, or is there another option I haven't considered?

A general solution would be preferred but I'm only really concerned with GCC and Clang.

Chris Hunt
  • 3,840
  • 3
  • 30
  • 46
  • Macros don't comply with rules of scope. One effect of this is that they don't behave differently depending on the scope they are used in - unfortunately, that is exactly what you are asking for. Within standard C++, you will need to stick with two macros - one that is used within a function, and one used at file scope (outside a function). `sizeof` is an operator in the language, so cannot be tested by the processor (`sizeof expression` is evaluated in a phase of compilation after the preprocessor is done). – Peter Aug 12 '18 at 23:36
  • @Peter, it's not necessary that the macro itself expand differently based on context - just that the value it expands to has different behaviors in function vs class/namespace context. With that limitation we are free to use constructions that use facilities available after preprocessing. You can see the example in my post which gets pretty close but does not go all the way, as I cannot use it in a shared header for example. – Chris Hunt Aug 13 '18 at 00:45
  • `__func__` is not a macro, it is a pre-defined function-local *variable*. Therefore you cannot use this name outside a function scope. I don't think there's any standard way to do what you want to achieve, you'll have to use two separate macros, as @Peter suggested. – Andrey Semashev Aug 13 '18 at 09:18
  • Non-standard is okay, at this point I just want to get to the end of the exercise. Testing with gcc shows that `__func__` evaluates to an empty string outside of functions, with a few warnings that may be suppressed with `_Pragma`. These unfortunately don't bring me closer to overcoming the issues mentioned for approach 2 above. I need some kind of homograph that implies the statement gets executed every time in a function context but not elsewhere. – Chris Hunt Aug 14 '18 at 04:46

0 Answers0