Reader Q&A: What does it mean for [[attributes]] to affect language semantics?

Followup on this earlier question, @bilbothegravatar asked:

@Alf, @Herb – I don’t quite get the [[noreturn]] example. While it may (not) compile on VC++, (as far as I understand) it does not carry any semantic meaning, and, what’s more, it is *perfectly* safe for any compiler that sees [[noreturn]] to just ignore it — the runtime behaviour vs. VC++ shouldn’t be changed at all.

So how is [[noreturn]] in the same camp as “restrict” ??? (I agree it may(?) be in the same camp as final and override.)

I will quote Bjarne: (http://www2.research.att.com/~bs/C++0xFAQ.html#attributes)
> There is a reasonable fear that attributes will be used to create language dialects.
> The recommendation is to use attributes to only control things that do not affect the meaning
> of a program but might help detect errors (e.g. [[noreturn]])
> or help optimizers (e.g. [[carries_dependency]]).

Yes, this argument was used. I’m (even) more conservative than Bjarne on this one.

People spoke up for two main different views on the question, “what does it mean to say [[attributes]] should not affect language semantics?” For convenience I’ll label these as the “weak” and “strong” views. Both views agree that if a program is valid both with and without the attribute, it should have exactly the same meaning both ways. Where the two views/camps differ is on whether it counts as “changing the meaning of the program” if a program that is valid if the attribute is ignored is rejected (fails to compile) if the attribute is observed:

  • The “weak” view says that’s okay because it didn’t really change the meaning of code that could ever execute, it just caused it to fail to compile. (I know, [[noreturn]] seems like it’s doing that… but it actually doesn’t quite meet this definition in my opinion, see below.)
  • The “strong” view, which I strongly support, says that calling a program illegal not only is the most dramatic semantic change possible, but also should be considered a nonconforming extension because it rejects code that is legal in the standard. (I know, [[noreturn]] in particular is standard… but it’s still problematic for this reason, see below.)

On principle, I do not like opening the door to let compiler writers use [[attributes]] as an excuse to not respect legal ISO C++ programs. And I say that as someone who works on a C++ compiler team and actively does C++ language extension design, and having the excuse of disabling part of ISO C++ that I don’t like by throwing in an [[attribute]] could be useful. Don’t get me wrong, there are corner cases of C++ I would like to turn off, but I try hard (and so far successfully) to refrain from doing such a thing without ISO C++’s blessing because it would break source code portability. Encouraging the view that nonstandard [[attributes]] can be a legitimate excuse to reject conforming C++ programs strikes me as putting a live grenade in a public square with a “pull me” tag on the pin. The whole point and primary benefit of having an ISO standard for C++ is to guarantee source portability; this weakens that, and for that reason I view it as a dangerous position to take.

However, in general the “weak” interpretation prevailed, and on those grounds [[noreturn]] and [[carries_dependency]] remained an attribute. I didn’t fight it because at least we got to remove [[final]], [[override]] and [[base_check]] as attributes, which was my primary concern since those would see far more use, and as long as we fixed those I was happy to say I could live with the others in order to get a consensus standard.

Post-Mortem: Assessing [[noreturn]]

Disclaimer: The following is an informational analytical exercise, not a public lobbying for a change. I support where we ended up as a whole to get C++11, it is probably too late to tweak [[noreturn]], and I consciously didn’t pursue making the following arguments at the time because [[noreturn]] (and [[carries_dependency]] are so rarely used that I can live with them, and you have to be willing to give some ground to get a consensus standard – the important thing was to get [[final]], [[override]], and [[base_check]] out and then stop while you’re ahead.

With that disclaimer in place, let me present counterarguments to show why I believe that in an ideal world [[noreturn]] shouldn’t be an attribute because it really is a keyword in [[attributes]] clothing and doesn’t actually meet either the weak or the strong view:

  • I don’t think that [[noreturn]] meets the bar set by the weak view, because it does more than just cause programs to fail to compile. In my opinion, adding [[noreturn]] does change the meaning of a conforming program, because the C++11 standard says in 7.6.3/2: “If a function f is called where f was previously declared with the noreturn attribute and f eventually returns, the behavior is undefined. … [Note: Implementations are encouraged to issue a warning if a function marked [[noreturn]] might return. —end note ]” – Even if respecting a [[noreturn]] did cause a compiler to reject some code (which is not required by the standard, and is not even mentioned even in the non-normative note which just talks about maybe issuing a warning), that means that void f(); and [[noreturn]] void f(); do not have the same semantics – returning from the first is always defined behavior (if you do it), and returning from the second is undefined behavior. This isn’t just a language-lawyerly argument either – the reason the standard says “undefined behavior” here is because that’s how the standard explicitly gives latitude to optimizers – it’s saying that “a compiler optimizer may assume the function doesn’t return, and optimize the code in ways that could cause different execution (even catch-fire semantics) if f ever actually does return.” Telling the compiler it may rely on this attribute to have meaning is, to me, clearly giving the attribute a language meaning and so changes the program’s semantics if it is present. So I don’t think it’s actually true that the presence of [[noreturn]] doesn’t affect language semantics.
  • It also doesn’t meet the strong view and discourages portability, by opening the door for using nonstandard attributes as a reason to reject conforming code. Now, it’s true that [[noreturn]] is in the standard itself, and so we might be tempted to say it’s not like a nonstandard attribute in this way that reduces portability, but it is – you cannot reliably write portable C++11 code under the assumption that [[noreturn]] has no semantic meaning and can be ignored. That’s because adding [[noreturn]] really does change the meaning of a function declaration (by adding guarantees that the optimizer can use, as described above) and so you really need to treat it as though it were a language keyword – because it is, just dressed in [[attributes]] clothing.

So in my view [[noreturn]] is a keyword dressed in [[attributes]] clothing.

Having said all that, experts do disagree, and the two camps have simply had to agree to disagree on this question – we got unanimous consensus on the standard, even though [[noreturn]] and [[carries_dependency]] are a bit of a sore point, because everyone was satisfied enough that we at least averted having [[final]], [[override]] and [[base_check]].

9 thoughts on “Reader Q&A: What does it mean for [[attributes]] to affect language semantics?

  1. So why wasn’t noreturn changed to a keyword? What were the arguments against it?

  2. @Olaf: See above — because what I’m calling the “weak” view prevailed, and people felt it fit the weak view, and those of us who disagreed decided not to be disagreeable and just live with it because it’ll be so rarely used. I’m speaking up about it, not to try to fix it, but to try to discourage doing any more of the same. :)

  3. “I don’t think that [[noreturn]] meets the bar set by the weak view, because it does more than just cause programs to fail to compile.”

    What’s interesting about this is that the [[carries_dependency]] part of the spec carries this notation: The carries_dependency attribute does not change the meaning of the program, but may result in
    generation of more efficient code.” Conspicuously, [[noreturn]] *does not* have that note.

    This is an interesting argument. It explains why `alignas` is a keyword and not an attribute (besides the fact that it needs an argument, which I don’t think attribute syntax allows). Because a compiler should not be allowed to silently ignore it. If it doesn’t support the specified alignment (or even user-defined alignment), then compilation *must* fail.

    After all, you didn’t write that `alignas` statement for your health; you did t because you wrote some code that will *fail* without that statement. If the compiler cannot execute the `alignas` directive, then the compiler *cannot* compile your code.

    I’m much more inclined to the use of context-specific keywords than stuff like attributes in these cases.

  4. @Herb: So if you had to summarize the core of what you’re saying, would you be able to say that [[noreturn]] is a semantically-meaningful annotation for functions, pretty much in the same way as const is a semantically-meaningful annotation for variables?

    I suspect my answer to that is “no”. The reason being that const becomes a tangible element in the language at a functional level. Its presence affects types and overloading, for instance. Whereas [[noreturn]] does no such thing. It only affects the underlying code generation, and the possibility of getting diagnostics (always warnings, not errors) resulting from the availability of more information to the compiler (the programmer said this).

    In contrast, final and override can very much acquire real essence in the program, like const does. It can affect correctness. You very much want errors when they are mis-used or mis-introduced.

    If you could describe an example of an attribute that you think really should be an attribute, and why, I think it would help me to understand the distinction between “has semantic meaning” and “doesn’t have semantic meaning” as you see it. I can think of candidates…

    __restrict: no (same reasons as const) [that’s __restrict in the way VS2010, for instance, supports it — I don’t think it got into C++11, right?]
    dllimport: I’m uncertain. It does seem to change the compiler’s behavior in some ways (different calling method?), affecting optmizations, and that’s your argument with noreturn, but in this case it’s more subtle.
    __forceinline: also directly related to optimizations.
    __assume: could this be an attribute? In any case, also optimizations.
    put_this_variable_in_the_fast_memory_bank: You could think of this sort of target-specific desired behaviors as useful. But there is (some) semantic meaning.
    bigendian: Wouldn’t it be nice to get endianness handled, sometimes? (as an Xbox 360 developers, I say yes). But it changes the meaning of the data (say, in the face of reinterpret_cast).

    However, in general, it seems to me that, if it doesn’t change the semantic meaning of the code in any way, it’s hard to consider an attribute useful in any way.

    On the other hand, I do think that some cases of limited semantic meaning useful. Now we just need to delineate the subgroup of semantic meaning changes that are acceptable to tolerate in something that attributes. For instance, if adding or removing an attribute can make compilation of a correct program (correct as of its intent, not the letter of the standard) fail, that’s probably a nono. But if adding an attribute to an incorrect (but legal) program causes the compiler to issue warnings (or maybe even errors, in some cases, maybe), I don’t think we should discount the usefulness of the attribute.

    In the case of [[noreturn]], if we want to be thorough, we have four possible scenarios:

    1- The function is meant to return, and doesn’t have [[noreturn]]
    2- The function isn’t meant to return, and doesn’t have [[noreturn]]
    3- The function is meant to return, and has [[noreturn]]
    4- The function isn’t meant to return, and has [[noreturn]]

    And we need to describe the behavior of a conforming compiler that either implements or ignores this attribute.

    If we can do this satisfactorily, then I’d be fully satisfied with this as an attribute. Let’s see:

    1- This is the trivial case.
    2- The compiler might or might not have enough information and smarts to display a warning like “unreachable code detected” if the caller does anything after calling the function. Same when compiling the function, it might want to emit a compilation warning if it detects that the function can’t return, to tell the programmer that the annotation should be added.
    3- The compiler is free to assume that the function doesn’t return and optimize at the call site, and it’s required to fail compilation or execution of the function. There are some cases that can only be detected at runtime, and it’s appropriate to substitute the return instruction with an exception throw or similar, to catch it at runtime.
    4- The compiler is free to assume that the function doesn’t return and optimize at the call site, and the function must compile without errors or warnings. If the compiler can’t determine that the function can’t possibly return, it must substitute the return instruction with an exception throw or similar, to catch any leftover programming errors at runtime.

    If the compiler doesn’t implement [[noreturn]], then cases 3 and 4 are handled like 1 and 2 respectively.

    This is all a bit of off-the-hip shooting, but I can’t help to feel that there’s a perfectly reasonable and tight definition for [[noreturn]] that works reasonably well with some equally reasonable definition of how a compiler must deal with unknown attributes. And the key here is _that_ definition of what an attribute is and how it can be ignored. “has no semantic meaning and can be ignored” is way too restrictive, and I do suspect is next to useless. “has _limited_ semantic meaning and can be ignored”, with an appropriate definition of “limited” in this context can be very useful in practice.

    Note also that there are at least two different “kinds” of annotations that I can think of. General-purpose annotations (ignore is ok, if you must, like [[noreturn]]) and special-purpose annotations (their handling as specified is required, or else the compiler must

    I hope I’m making sense :).

  5. @JCAB: As I mentioned earlier, I was against having attributes at all. :) But to answer what I might consider legitimate uses of them, things that really are whitespace to the C++ language and C++ compiler — I wouldn’t be opposed to using them for doc-gen type comments, or other similar annotations that the compiler may pass along for other tools to absorb but that the compiler itself doesn’t make use of.

  6. “There is a reasonable fear that attributes will be used to create language dialects”

    Who are we trying to kid here? In what sense do we not _already_ have C++ dialects?

    Are we so indoctrinated that we no longer see all of the #ifdef’s and #define’s required to make “normal” code truly portable (I’m looking at you, __decl_spec(dllexport)!)?

    This entire conversation makes no sense to me. I thought [[attributes]] was an attempt to normalize these extensions as a syntactic construct so we stop denying the reality that is vendor-specific extensions. Or am I totally misunderstanding the purpose?

  7. “If the compiler doesn’t implement [[noreturn]], then cases 3 and 4 are handled like 1 and 2 respectively.” And that’s the problem. What is undefined behavior on one compiler *is* defined behavior on another.

    You’re focused on the functionality of [[noreturn]] (which is fine) and not the fact that it isn’t a *keyword* (which is the problem).

    Attributes should not affect the *language*. If we had an attribute for dllexport, it would be used for exactly that: exporting functions in DLLs. And while this touches the compiler, it doesn’t touch the C++ part of the compiler; it just modifies how the compiler generates code a bit. The language itself works the same whether the compiler recognizes [[dllexport]] or not.

  8. @jmckesson: You say “What’s undefined behavior on one compiler *is* defined behavior on another.” as if that’s a toxic situation. But in fact, this is true of MOST undefined behavior. Because there is no restriction on what a compiler may do when it encounters code which formally has undefined behavior, the compiler may define implementation-specific consistent behavior. The code is still non-portable, even to new compiler versions, because it evokes undefined behavior, but some compilers act predictably. In that sense, there’s no difference between [[noreturn]] and say misuse of reinterpret_cast on function pointers.

Comments are closed.