Document number:  N2634=08-0144
Date:  2008-05-14
Author:  John Spicer, Edison Design Group
 [email protected]
 J. Stephen Adamczyk, Edison Design Group
 [email protected]

Solving the SFINAE problem for expressions

Introduction

Way back in March of 2002, Core Issue 339 asked whether arbitrary expressions (for example, ones that include overload resolution) are allowed in template deduction contexts, and, if so, which expression errors are SFINAE failures and which are hard errors. For example:

template <int I> struct A {}; char xxx(int); char xxx(float); template <class T> A<sizeof(xxx((T)0))> f(T){} int main() { f(1); } This example is rejected by all major compilers, in spite of the fact that the C++2003 standard does not appear to contain words that prohibit it.

Why is that? Well, partly implementation difficulty and partly uncertainty on the part of compiler developers about whether such cases are valid. (No one wants to put significant effort into implementing something and then find out from the standards committee that the work was unnecessary.)

Why is is there a question about why such cases would be valid?

First, because it's hard to implement the general case. Template deduction/substitution has historically used a simplified model of semantic checking, i.e., the SFINAE rules (which are mostly about types), instead of full semantic checking. This limited semantic checking is typically done by completely separate code from the code for "normal" expression checking, and it's not easy to extend it to the general case. "Speculative compilation" sounds like an easy way out, but in practice compilers can't do that.

Second, because it affects name mangling and therefore the ABI. (Current versions of gcc reject the example above largely, it appears, because there is no defined name mangling for the call operator in the Itanium C++ ABI spec.)

Third, because we need to figure out what to say and how to say it in the standard.

This has been mostly a fringe issue so far, because it was limited to sizeof, but that has changed with the addition of some C++0X features: decltype makes the problem worse, because the standard use case is one that involves overload resolution; generalized constant expressions make it worse yet, because they allow overload resolution and class types to show up in any constant expression in a deduction context.

The proposed solution

At the Oxford meeting, the Core Working Group was headed toward a solution that imposed a restriction on expressions in deduction contexts, but such a restriction seems to really hamper uses of constexpr functions. So, at the Toronto meeting, we decided instead to propose that fully general expressions be allowed, and that most errors in such expressions be treated as SFINAE failures rather than errors.

One issue with writing standards wording for that is how to define "most". There's a continuum of errors, some errors being clearly SFINAE failures, and some clearly "real" errors, with lots of unclear cases in between. We decided it's easier to write the definition by listing the errors that are not treated as SFINAE failures, and the list we came up with is as follows:

  • errors that occur while processing some entity external to the expression, e.g., an instantiation of a template or the generation of the definition of an implicitly-declared copy constructor
  • errors due to implementation limits (it's probably a category error to list these here, since they're not errors in the normal sense, but we wanted to make it very clear that compilers don't have to take steps to capture and recover from violations of implementation limits; such violations cause hard errors, compiler crashes, etc., the same as anywhere else in a program).
  • errors due to access violations (this is a judgment call, but the philosophy of access has always been that it doesn't affect visibility)
  • Everything else produces a SFINAE failure rather than a hard error.

    There was broad consensus that this felt like a good solution, albeit one with significant implementation cost.

    One additional issue

    The current Working Draft contains, in 14.8.2 [temp.deduct] paragraph 2, a rather long list of errors that might come up as a result of type substitution and which should result in SFINAE failures rather than hard errors. This is, in effect, the "type errors" list that complements the "expression errors" list proposed by this paper. It is written in the form of an exhaustive list of errors rather than as a list of exceptions to the general principle that all errors are SFINAE failures.

    For consistency, it would be nice if both lists were written in the same way, but we have some fear that changing the type-errors list may inadvertently break some existing code. We struggled with that for a bit, but decided that it made more sense for the long term to have both lists written in the same way, and we have the belief that the current list is intended to be complete (indeed, it has been corrected a number of times to make it more complete), so we think if there's a difference between the two specifications it will probably be due to a bug in the old list.

    So, we propose wording changes for that section too, which mostly amount to making the existing rules into notes. (We think having some version of the rules there is helpful as a checklist for implementors.)

    Working Draft changes

    Replace 14.8.2 [temp.deduct] paragraph 2 bullet 3 with the following (all the sub-bullets of bullet 3 are also removed from here and moved to the location indicated below):

    The specified template argument values are substituted for the corresponding template parameters as specified below.

    Add the following after 14.8.2 [temp.deduct] paragraph 5:

    At certain points in the template argument deduction process it is is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

    The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. [Note: The equivalent substitution in exception specifications is done only when the function is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression.]

    If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. Access checking is not done as part of the substitution process. Consequently, when deduction succeeds, an access error could still result when the function is instantiated. Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the "immediate context" and can result in the program being ill-formed.]

    [Example:

    struct X {}; struct Y { Y(X){} }; template <class T> auto f(T t1, T t2) -> decltype(t1 + t2); // #1 X f(Y, Y); // #2 X x1, x2; X x3 = f(x1, x2); // deduction fails on #1 (cannot add X+X), calls #2 -- end example]

    [ Note for editor: Move list of type substitution failures from 14.8.2 [temp.deduct] paragraph 2 following the third bullet to this location, as a note. ]

    End of document.