C/C++ Contributing Editors


Uncaught Exceptions: Your Witness

Bobby Schmidt

You'll be glad you looked at these insights on name lookup. Just don't let Bobby catch you whining.


Copyright © 2001 Robert H. Schmidt

One of the risks attached to this Q & A column is reader audacity. The column’s interactive nature seduces y’all into believing I actually want you second-guessing my ivory-tower proclamations. Some of you even attempt to publicly invalidate my arguments and rebut my conclusions. Such heresy! Such cheek! Such opportunity for a column theme!

Outs and Ins

Q

Ahem. I was reading the August issue, and I disagree. In the “Ins and Outs, Part 2” section, you appear to imply that the problem is related to templates. It’s not. It’s a general problem with scoping of non-templates. Consider this:

class Outer
    {
    class Inner;
    static Inner data;
    Inner &GetInner(Inner);
    };

Outer::Inner Outer::data;

Outer::Inner &Outer::GetInner(Inner)
    {
    return data;
    }

Just like in the example you wrote about, the return type must be qualified in the definition, whereas the function argument doesn’t need to be — same thing with the type of the static data member. Templates play no part in it whatsoever.

Salutaciones — Juan Carlos Aruvalo Baeza

A

The specific problem reader Hahn encountered was related to templates. But I didn’t mean to imply the general problem of name lookup and qualification is related to templates. Indeed, as I wrote in Footnote 7 of that same column:

[The problem’s existence] is true even for non-template types. For example, if GetData returned inner rather than vct<inner>, the lookup problem would be the same.

So I really don’t think we are in disagreement. However, as I also wrote in that footnote, I do believe the problem is exacerbated by templates.

Templates and non-templates have sometimes-subtle differences in name-lookup rules. As I suspect most of us are more familiar with the non-template rules, I see greater opportunity for naming error and misunderstanding lurking in templates.

Further, the problem touched on file organization and declaration ordering. That condition is often aggravated by header files and their inclusion order. Given that we tend to put entire template definitions in headers — something we don’t as often do for non-templates — I again suspect more opportunity for naming mischief exists when templates are involved.

Outs and Ins Again

Q

In your August CUJ column, you cite me as remarking on how name lookup rules vary for templates and functions.

Though I’ve been known to complain about name lookup rules with respect to templates (especially the rule that names in derived classes aren’t looked up in base classes that are instantiated from templates), I think what you are referring to is my whining about the difference between the rules for template argument deduction and function overloading resolution.

That is, what annoys me is that this will compile:

set<int>::iterator si;
set<int>::const_iterator sci;

void f(set<int>::const_iterator,
        set<int>::const_iterator);

// fine, iterator is converted
// to const_iterator
f(si, sci);

but this won’t:

// Error! iterator is NOT converted
// to const_iterator
std::distance(si, sci);

They’re both function calls. But the rules governing what is happening are completely different. — Scott Meyers

A

Oh, probably. You’ve whined about so much for so long, I’ve lost track!

Speaking of which...

extern "C+-"

Q

I don’t think your discussion of the semantics of extern "C" holds water [1]. Let me explain why I care. This is from Item 34 of More Effective C++ (Addison-Wesley, 1995):

The best way to view extern "C" is not as an assertion that the associated function is written in C, but as a statement that the function should be called as if it were written in C. (Technically, extern "C" means the function has C linkage, but what that means is far from clear. One thing it always means, however, is that name mangling is suppressed.)

...

You can even declare C++ functions extern "C". This can be useful if you’re writing a library in C++ that you’d like to provide to clients using other programming languages.

As you can see, I have a vested interest in this issue.

Linkage is defined in C++ Standard Subclause 3.5, where it’s clear that linkage is a property of a name. In particular, there is nothing said about it being a relationship between two entities (except for Subclause 3.5/11, which I’ll get to in a minute).

Hence it makes no sense to say that X links to Y or Y links to X.

Rather, X may have linkage and Y may have linkage. Linkage matters only for name lookup; whether a name is found during linking depends in part on the linkage of that name.

Subclause 3.5/11 brings up the notion of linking “to” declarations for entities written in other languages. But what does this mean? Given the rest of Subclause 3.5, it can mean only one thing: when name lookups occur during linking, the names found may correspond to entities implemented by other languages. That is, Subclause 3.5/11 does nothing more than suggest that a C++ program may include entities implemented in languages other than C++.

Let us thus trot off to Subclause 7.5.

Subclause 7.5/2 introduces the notion of linkage “between” code fragments. Note that according to the usual rules of English, “between” is commutative: if X is between a and b, X is also between b and a. Hence, Subclause 7.5/2 could just as easily have been started this way: “Linkage between non-C++ and C++ code fragments....”

(As an aside, my favorite part of Subclause 7.5/2 is “The semantics of a language linkage other than C++ or C are implementation-defined.” Where does the Standard define the semantics of C++ and C linkage, especially given Subclause 7.5/9? On this matter, I stand by my statement in More Effective C++ above.)

Of course, Subclause 7.5/3 is key. It requires support for “linkage to functions written in the C programming language.” But Subclause 3.5 makes clear that linkage affects only the ability to perform name lookup during linking. I thus read Subclause 7.5/3 this way:

If you assign “C” linkage to an entity written in C++, that entity can be found (via name lookup) during linking to functions written in the C programming language.

Thus, declaring a C++ function extern "C" is a way of making it callable from C, as far as the Standard is concerned. As you point out in your column (and I point out at the beginning of More Effective C++ Item 34), there’s a lot of stuff the Standard is not concerned with, but I think it’s clear that extern "C" is intended to allow exactly the kind of thing that Phil Brooks and Jon Young said it was (provided the underlying C and C++ object models are compatible).

If I’m correct about the above, feel free to admit it in your column and supplicate yourself. If I’m not, well, run it anyway and dump on me the way you know I’d dump on you if it were my column :-) — Scott Meyers

A

When I originally wrote about this topic, I failed to clearly distinguish between what the C++ Standard guarantees and requires, and what I think the Standard intends. This time around, I hope to make that distinction clear. If you must, consider this mild supplication.

The C++ Standard requires that a C++ implementation allow entities to be declared extern "C". But I can find no requirements or guarantees about what such declarations mean absolutely for a given C++ implementation, or how the entities behave relative to entities declared with “C++” linkage. The only specific references I can find are:

On extern "C" the Standard gives no other guidance, rationale, or example, for either compiler vendors or users. In particular, the Standard explicitly neither endorses nor prohibits extern "C" as a mechanism allowing:

We must therefore infer whatever intent the C++ standardization committee has for extern "C". Such inference is fuzzy at best, something reasonable and informed people can disagree about, as we have.

Specifically, as you point out, Subclauses 7.5/2 and 7.5/3 together suggest the intent that C and C++ have bidirectional “linkage” to one another. However, I’m not convinced that “linkage” in Subclause 7.5 means the same thing as “linkage” in Subclause 3.5. The latter is all about declarations and scopes and translation units — concepts that I don’t see applying to entities untouched by a C++ translator. Accordingly, I find “linkage” an undefined or ill-defined term in the context of extern "C" [5].

Even so, I’ll concede that “linkage” probably includes the intent (but not the guarantee) that C can call C++ functions and C++ can call C functions, at least for the same implementation. However, specifying the mechanism or means for such calls strikes me as beyond the Standard’s intent [6].

From a less lawyerly perspective, I think the significant questions are these:

If the answer to all of these is “yes,” then I think we have a pretty good working definition of extern "C" intent.

In sum, the Standard requires and guarantees little of extern "C" beyond its mere existence and explicitly intends nothing beyond “linkage to functions written in the C programming language.” C++ vendors are thus free to implement and purpose extern "C" as they see fit. In practice, vendors of joint C/C++ implementations should almost certainly allow inter-language calls on the same implementation, might allow calls to languages other than C and C++ on the same implementation, and might allow calls to other implementations.

D Flat

Q

Bobby,

Regarding “Headbangers Ball” in the March 2001 issue of “Uncaught Exceptions,” you missed answering the question “In what way would read-only access by default be a violation of encapsulation?”. By not answering, you risk making this question appear rhetorical with implied endorsement of this idea.

Such a situation does violate encapsulation because users of private-but-read-only items could become dependent on data type. For example, a private: int i changing over to a double i can cause surprises for the read-only users of i.

That said, your scheme of having a public: const int& i that is initialized to point to the private read-write version can avoid surprises. A change in type could be handled by keeping a separate private: int i2 mirrored from the real i during all updates to i.

This does however seem error-prone compared to a get method that handles the conversion from double to int in a single place.

Cheers — Claude Brown

A

The original reader (Dan Watkins) wanted to make a data member read/write inside member functions, but read-only elsewhere. Along the way, he wondered why private data members weren’t available as read-only to the outside world. He reckoned this would bring two benefits:

You suggest that such read-only data increases coupling, because users would be dependent on that data’s type. Strictly speaking, this is true — there is no insulating abstraction layer in:

class X
    {
public:
    int i;
    };

X x;
int i = x.i;

However, if an accessor function takes the path of least resistance and simply returns that type it’s encapsulating:

class X
    {
public:
    int get_i() const
        {
        return i;
        }
private:
    int i;
    };

X x;
int i = x.get_i();

the same coupling is there.

The second version is more insulated against change, provided the maintainer takes advantage of that insulation:

class X
    {
public:
    int get_i() const // no change
        {
        return (int) i; // change
        }
private:
    double i; // change
    };

X x;
int i = x.get_i(); // OK, no change

But if the maintainer changes the return type of get_i to double, the abstraction is defeated, and the original coupling persists.

I’ll end slightly off-topic, by noting that C# has a hybrid form:

class X
    {
    public int i
        {
        get
            {
            return (int) i_;
            }
        }
    private double i_;
    }

// ...

X x = new X();
int i = x.i; // OK

In the parlance of C#, i is a property. In C++ terms, a property has the syntactic appearance of a data member, but the implementation of a function member. (The semantics are a hybrid.) C#’s designers introduced properties specifically to address the problems both you and Reader Watkins cite.

Notes

[1] This is in reference to my June and September columns.

[2] Although this appears to be the most credible use for extern "C" on code implemented in C++, the Standard never makes the intent explicit.

[3] I suppose you could infer the intent here via the C library support for extern "C", since parts of the library may well be written in an assembly language, yet act like they’re written in C. But again, the Standard never makes the intent explicit.

[4] The Standard makes no mention of name mangling or name decoration in any context.

[5] As both you and the Standard equate the Subclause 7.5 use of “linkage” and the Subclause 3.5 use of “linkage,” I’m probably setting myself up for another supplication here!

[6] Name mangling is a means to linkage, not an end. I’m certain the Standard’s authors expected name mangling to be the vehicle of choice in many implementations, but that’s different from them intending or assuming name mangling as the vehicle.

[7] By that I mean: no C++ startup/shutdown requirements, no reliance on the Standard C++ library, “C” locale only, no exceptions, maybe no templates, same object layout, same naming convention, same calling convention, and common or compatible parameter/return types. (There are probably more, but you get the idea.)

Although Bobby Schmidt makes most of his living as a writer and content strategist for the Microsoft Developer Network (MSDN), he runs only Apple Macintoshes at home. In previous career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him on the Internet via BobbySchmidt@mac.com.