Proposed addition of __func__ predefined identifier from C99 Doc No: SC22/WG21/N1534=03-0117 Date: November 17, 2003 Project: JTC1.22.32 Reply to: Alex Rosenberg (formerly of Apple Computer, Inc.) alexr@spies.com Abstract: Adoption of C99's __func__ predefined identifier and modifications to the C99 definition to enhance it's capability in C++ 1. The Problem On-going compatibility with C is important for C++, both for ease of upgrading a program to C++ and for support of compilers that implement both languages. C99 introduces several new features which breaks compatibility. Many compilers already implement some support for C99's __func__ predefined identifier in C++ without the benefit of standardization. Portable ways to debug C++ programs are an area where the language is lacking. Improved debugging facilities assist in all manners of use of C++ as debugging is debatably the largest proportion of time spent during development. 2. The Proposal 2.1 Basic Cases Much like the preprocessor provides __FILE__ and __LINE__, some compilers have extended the notion with additional predefined macros such as __FUNCTION__ and __PRETTY_FUNCTION__. C99 adds the notion of a "predefined identifier" and defines just one, __func__. The intent of __func__ is similar to __FUNCTION__, but the delivery is different. To quote ISO/IEC 9899:1999: 6.4.2.2 Predefined identifiers 1 The identifier __func__ shall be implicitly declared by the translator as if, immediately following the opening brace of each function definition, the declaration static const char __func__[] = "function-name"; appeared, where function-name is the name of the lexically-enclosing function. 2 This name is encoded as if the implicit declaration had been written in the source character set and then translated into the execution character set as indicated in translation phase 5. 3 EXAMPLE Consider the following code fragment: #include void myfunc(void) { printf("%s\n"), __func__); /* ... */ } Each time the function is called, it will print to the standard output stream: myfunc This basic feature from C99 requires some modification to integrate with C++. The common presence of "mangled" names and of language features such as strict typing, overloading, operators, and templates present a quandary regarding the content of the string. The author believes that this is the "bicycle shed" for this proposal and as such, I would propose that this is substantially similar to std::type_info and should be dealt with by declaring the string contents to be implementation-defined with some suggestions. Firstly, that the implementation should define a predictable and documented naming scheme. If they choose to emit mangled names, they should document their mangling scheme and provide a means to demangle a given string. Secondly, they should endeavor to guarantee that these strings are unique in a program. This has impact on static functions, anonymous namespaces, and local or anonymous classes, for which they should consider including the file name containing the translation unit in the string. 2.2 Advanced Cases C99 does not provide for the existence of __func__ where there is no lexically-enclosing function as occurs with static initialization, namespace scope, etc. It is proposed to modify C99's definition to define that __func__ also works from static initialization and provides a unique but implementation-defined string (most likely including the file name of the translation unit). For example: // at namespace scope const char foo[] = __func__; In this case, foo shall contain an implementation-defined string that uniquely identifies the file-level scope where it is initialized. The file name or a full or partial path to the file are typical implementation choices. C++'s 8.3.6/7 says "Local variables shall not be used in default argument expressions." It is further proposed that C++ allow __func__ to be referenced in a default argument expression. This establishes that __func__ is defined in every possible situation in which an expression may occur. 2.3 Motivating Examples The combination of the two modifications presented in section 2.2 permit two motivating examples: 2.3.1 Portable Stack Crawls There exists no means to perform any form of portable stack crawl. This is a major omission from the language. Every non-portable stack crawl solution the author has encountered exposes all manner of implementation details and offers little more than would be obtained doing assembly-level debugging. list crawl; void dump_stack_crawl(void) { for (list::iterator i = crawl.begin(); i != crawl.end(); ++i) { cout << *i << endl; } } void f(... , const char* caller = __func__) { crawl.push_back(caller); crawl.push_back(__func__); ... crawl.pop_back(); } Here, f() or similarly instrumented functions and their unmodified callers are tracked in a portable partial stack crawl. With judicious use of the preprocessor, such a tracing facility can be selectively enabled or disabled to narrow the scope of debug output. 2.3.2 Mutation Tracking Given a very complex data structure, such as the nests of DAGs found in the back-end of an optimizing compiler, a common debug scenario is to wade though dumps of the structure after each possible transformation, hunting for the point at which something went wrong. With multitudinous small transformations being performed, this can be like looking for a needle in a haystack. Given that these structures are often comprised of many interlinked nodes of objects sharing a common base class, one can envision such an example: class Node { ... virtual void perform_some_transformation(const char* caller = __func__); ... private: const char* last_mutation; } Node a; Each caller would then call a->perform_some_transformation(). Each Node in the complex structure would then remember who last asked that it be changed. Similar manually-performed tracking has proven beneficial to the author in the past. This can be accomplished today in C++ using __FILE__ and __LINE__ combined with other preprocessor machinations, but these strings are often different among the differing versions of code being worked on by a multiple engineer team. Function names are much less likely to change and are more useful with modern integrated development environments that shun the traditional text file editor model in favor of an object browser. 3. Interactions and Implementability 3.1 Interactions As noted in footnote 60 in C99, since __func__ begins with a double underscore, it was already reserved by the implementation. As a result, no backward compatibility issues are expected. The exception to 8.3.6/7 does not completely create a stack crawl facility. No solution for destructors, overloaded operators, or unmodified library functions is provided. 3.2 Implementability Several compilers already implement C99's __func__ in some C++ compilation modes. None that the author is aware of implement the extensions proposed, although at least one (gcc) allows the use of __func__ at global scope with a warning, producing an empty string. 4. Extension Ideas Following an earlier draft submitted to the reflector, it occurred to the author that __func__ doesn't tell the whole story for scopes in C++. Other major scoping constructs such as classes/structs and namespaces are not addressed by __func__. Obvious motivating examples for other support of other scopes include serialization and dynamic dispatching by name. While there may be more specific or more powerful solutions to these examples on the horizon, it is believed by the author that these represent a reasonable extension of the intent of C99's __func__ and should be considered as well. 4.1 __class__ __class__ would be defined as if it appeared just inside the opening brace of the lexically-enclosing class/struct definition as: public: static const char __class__[] = "class name"; private: Where "class name" is the name of the lexically-enclosing class/struct and the access specifiers are omitted inside structs. Similar to __func__, the string contents would be implementation-defined to accommodate situations like anonymous classes/structs and locally-defined classes/structs. At namespace scope, __class__ shall return an implementation-defined string, possibly the same as __func__ would return in the same context. This would permit __class__ to be usefully used as a default argument. 4.2 __namespace__ __namespace__ would be defined as if it appeared just inside the opening of the lexically-enclosing namespace block: static const char __namespace__[] = "namespace name"; where "namespace name" would contain the name of the fully-qualified lexically-enclosing namespace. For example: namespace A { namespace B { void foo(void) { cout << __namespace__ << endl; } } } A::B::foo() would emit "A::B". As before, anonymous namespaces and namespace scope would cause the string to be implementation-defined.