The Case of Spicer’s Troubled Friend

Merijn Laks

The glow of the monitor was the only light left in the office, casting a looming shadow over a stack of C++20 defect reports. We were digging through the C++23 standard core language features, specifically a heavy piece of paperwork known on the streets as P1787R6, a.k.a. Declarations and where to find them. Eighty-four cold hard cases bundled together into one massive brief, dealing in definitions, lookup completeness, complete-class context, module matching, parsing, and templates.

Usually, a Defect Report is a straightforward job. A little cross-referencing, some detective work, and you close the file. But once in a blue moon you stumble across a file like CWG1477. You pry the lid off, and it’s a can of worms that just keep wiggling the more you look at the screen.

John Spicer walked this one in, way back in 2012, called it “Definition of a friend outside its namespace.” Take a look at the code he drew up and ask yourself: should this example walk free, or is it ill-formed, crashing and burning at the worst possible moment?

namespace N {
    struct A {
      friend int f();
    };
}

int N::f() { return 0; }
int i = N::f();

Let’s do some forensics: struct A declares a friend function in namespace N, then we perform a qualified name lookup to define f outside of N, and follow it up with an unqualified name lookup to make the call to f.

Spicer pointed out that according to this transcript in C++11, the last line of the example is a blind alley:

“The name of the friend is not found by unqualified lookup […] or by qualified lookup […] until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship)”,

 

But Spicer saw things differently; he argued the example he provided should be well-formed. He lobbied the authorities for new wording: “The friend declaration does not by itself make the name visible”, and got it pushed into the C++14 standard. Case closed, right?

Wrong. In standard compliance, nothing stays buried.

Just under two months after C++20 was officially approved, the P1787R6 proposal mentioned earlier reared its head, explicitly stating that the changes it describes will reject Spicer’s friend. By the time C++23 rolled around, P1787R6 was stamped and approved.

As part of the changes in P1787R6, Spicer’s wording had vanished from the normative text. However, contrary to what P1787R6 said in prose, the new wording technically still accepts Spicer’s example. The blood hounds over at the LLVM mailing list picked up the scent, too. Things weren’t adding up; the normative wording and P1787R6 were singing two different tunes.

We thought we found a smoking gun in [basic.scope.scope](C++23), a note seemed to support the rejection of Spicer’s troubled friend example:

“Friend declarations and declarations of qualified names and template specializations do not bind names (9.3.4)”.

It looked like the justification we needed to reject the code. But it was a ghost. A messy remnant from an older draft (P1787R4) that got rubbed out in a 2024 GitHub merge, long after C++23 was locked down. A cold lead.

So we shook down the open-source compilers to see who was playing ball. GCC wasn’t talking. No implementation of P1787, no plans to start. Clang was a different story, they had a file open on P1787 for a while. Our guy on the inside told us Clang was already playing by Spicer’s rules since the moment it got its own C++ implementation in version 2.7. When we turned up the heat, our tests confirmed it: GCC rejects the example, while Clang compiles.

Sometimes the compilers, the standard, and the defect reports can’t get their stories straight in the interrogation room. In this line of work, you don’t just trust what a person meant to say, you trust what’s black and white. A compiler has to follow the normative wording of the standard, otherwise the whole system can go rogue. That’s why we booked this example into our suite as a t-test, expecting a clean compilation. We uphold the law and keep our clients in the loop about any deviations in their compiler, making sure they never get caught in the crossfire.

When you’re building safety-critical systems, you can’t afford a compiler acting up based on a misunderstood footnote. A rogue interpretation is what gets people killed in this line of work. That’s exactly where we at Solid Sands step in, cutting through contradictory prose, the dead-end defect reports and the politics to get you the results that matter: hard proof of what the standard actually enforces.