Doc No:
SC22/WG21/N1642 04-0082
Date:
April 9, 2004
Project:
JTC1.22.32
Reply to:
Alex Rosenberg
  Sony Computer Entertainment America
  alexr@spies.com

Adoption of C99's __func__ predefined identifier and improved default argument behavior

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 break 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.

This document proposes adopting __func__ from C99 with additional specification of places in which it may appear. It also proposes changes to default arguments and provides additional contexts in which default arguments may be used.

Motivating examples are illustrated, including a portable stack crawl mechanism.

C99 Background

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 <stdio.h>
void myfunc(void)
{
  printf("%s\n"), __func__);
  /* ... */
}

Each time the function is called, it will print to the standard output stream:

myfunc

Proposal

NTBS Contents

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 NTBS 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.

Defined Wherever Expressions May Occur

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.

This enhancement in cooperation with the changes to default arguments allows for a more complete solution to the motivating examples.

Default Arguments and Deferred Behavior

"..default arguments are logically redundant and at best a minor notational convenience." [D&E, 2.12.2]

Default arguments have the unique property that a change made only in the called function definition may change all existing callers of the function. With this observation, default arguments border on both closures and AOP.

#include <iostream>
const char *foo(const char *f = __func__) { return f; }

int main(int argc, const char* argv[])
{
  std::cout << foo();
}

There are several potential choices for __func__'s value in this default argument. Since __func__ is already in a new class of identifier and C99 lacks default arguments, there is even more flexibility.

It is proposed here that f shall refer to the value of __func__ as if the name binding had occurred at the call site inside main. That is, this program shall print "main" or whatever main's NTBS is defined to be with the compiler being used.

This would require that __func__ be defined to be a "deferred identifier" instead of the "predefined identifier" terminology used in C99. It would be bound at the latest possible place and then subject to the same "predefined" behavior as found in C99.

Generalized Syntax

Instead of defining __func__ as a "deferred identifier" and creating specific new rules for this class of identifier to permit the uses described here, it is possible to define a generalized syntax for the name-binding rules described here.

One possible syntax would be "::::identifier" to bind to whatever is named as identifier in the context where a default argument is being expanded or the point of instantiation of a template default argument. The mnemonic here is that ".." refers to the parent directory in several major file systems. Other syntaxes are possible and this should just be considered illustrative.

With a generalized access to name binding in the expansion of a default argument of the point of instantiation of a template, __func__ and the other proposed "deferred identifiers" would simply be C99-style "predefined identifiers." To return to an earlier example:

#include <iostream>
const char *foo(const char *f = ::::__func__) { return f; }

int main(int argc, const char* argv[])
{
  std::cout << foo();
}

Would print "main" as before. To borrow from 8.3.6 p5:

int a = 1;
int f(int);
int g(int x = f(::::a));

void h() {
  a = 2;
  {
    int a = 3;
    g();         // g(f(::::a))
  }
}

g would be called with the value f(3).

Further examples in this document skip the generalized syntax and use the "deferred identifier" syntax instead, but either syntax would satisfy the needs of the motivating examples.

Default Arguments and Operator Overloads

One of the motivating examples below is a portable stack crawl facility. With the changes proposed thus far, operator overloads, destructors, and library functions cannot participate in such a stack crawl mechanism.

Operator overloads would be enhanced to support any number of arguments requiring default arguments for each one after the existing mandated arguments necessary for their normal syntactical use.

struct A {
  A operator+(const A& a, int b = 1);
  A operator+(int b = 2);
};
A operator+(const A& a, const A& a1, int b = 1);
A operator+(int b = 2);

The first operator of each pair is the usual binary operator for addition, accepting an additional default argument. The second one is the unary addition operator. Overload is as before, ignoring any arguments having a default. If all arguments have a default, it is as if the operator had a void argument list.

Explicit calls to operator overloads may substitute for the default arguments as with normal functions.

The only operator overload which this change does not work for is the function call operator.

Default Arguments and Destructors

As with operator overloads, destructors are similarly modified to accept an argument list consisting only of arguments having defaults.

Motivating Examples

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<const char*> crawl;

void dump_stack_crawl(void)
{
  for (list<const char*>::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.

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 graph 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.

Interactions

As noted in footnote 60 in C99, since __func__ begins with a double underscore, it was already reserved by the implementation. Defining __func__'s behavior in additional contexts such as namespace scope and default arguments runs the risk of future versions of C supporting incompatible behavior.

The example stack crawl facility is not complete as there is no solution presented for unmodified library functions nor for overloads of the function call operator.

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.

There exist several implementation possibilities for default argument handling in compilers. Although implementations like thunks and function cloning are possible, the author has only ever seen compilers that integrate the default argument expressions at each call site. Even if a compiler chooses a strategy like thunks or function cloning, that does not preclude the default argument rules presented here, although it would require them to adopt an integration strategy for at least the default arguments containing "deferred identifiers" or the similar generalized syntax.

Extension Ideas

Library Demangler

An earlier version of this proposal (N1534) as well as postings on comp.lang.c++.moderated from various authors suggested that an NTBS describing a specific source location was insufficient and more compile-time introspection was necessary. Toward that end, __class__ and __namespace__ were suggested.

It may well be better to propose a library-only solution in the std namespace to provide demangling and introspection of a given NTBS accessed via either __func__ or std::type_info->name(). No class design is presented here, but it bears mention in the context of this discussion.

Additional Deferred Identifiers

In addition to defining __func__ as a deferred identifier, __file__ and __line__ are proposed. They would take on their respective values at the call site for a default argument expansion, or the point of instantiation for a template default argument. These additional deferred identifiers permit the following type of code:

void assert(bool b,
            const char* file = __file__,
            long line = __line__,
            const char* func = __func__)
{
  if (!NDEBUG && !b)
  {
    std::cerr << "Assertion failure in " << func
              << " in file '" << file
              << "' at line " << line
              << "." << std::endl;
    abort();
  }
}

While this representative example can not be used as a replacement for assert as-is, a similar improved assert replacement could be constructed as a library extension, eliminating one popular use of the preprocessor.

It is also worth poining out that a constructor called from a throw-expression could easily participate in a portable stack crawl mechanism or similar using the facilities described here.

Citations

[D&E] Stroustrup, B. The Design and Evolution of C++. Addison-Wesley, Reading, MA. 1994. ISBN 0-201-54330-3