Channels ▼
RSS

"Export Restrictions, Part 2


November 2002/Sutter’s Mill


This is the second of a two-part miniseries. In the previous column, I covered the following [1]:

  • What export is, and how it’s intended to be used. We looked at an analysis of the similarities and differences between the “inclusion” and “export” template source-code organization models, and why they’re not parallel to the differences between inline and separately compiled functions.
  • The problems export is widely assumed to address, and why it does not in fact address them the way most people think.

Widespread expectations notwithstanding, export is not about truly “separate” compilation for templates in the same way we have true separate compilation for non-templates. Many people expect that export means that template libraries can be shipped without full source-code definitions (or their direct equivalent), and/or that build speeds will be faster. Neither outcome is promised by export.

The community’s most informed experience to date is that full source or its direct equivalent must still be shipped, and that build speeds are expected to be the same or slower in most cases. Why? Principally this is because dependencies, though masked, still exist, and the compiler still has to do at least the same amount of work in common cases. In short, it’s a mistake (albeit a natural one) to think that export gives true separate compilation for templates in the sense that the template author need only ship declaration headers and object code. Rather, what is exported is similar to Java libraries where the bytecode can be reversed to reveal something very like the source; it is not traditional object code.

This time, I’ll cover:

  • The current state of export, including what our implementation experience to date has been.
  • The (often non-obvious) ways that export changes the fundamental meaning of other apparently unrelated parts of the C++ language.
  • Some meager advice on using export effectively if and when you do happen to acquire an export-capable compiler.

But first, consider a little history.

Historical Perspective: 1988-1996

Given that there are some valid criticisms of export, it might be tempting to start casting derisive stones and sharp remarks at the people who came up with what we might view as a misfeature. It would also be ungracious, unkind, and could possibly smack of armchair quarterbacking. This part of the article exists for balance.

If export doesn’t deliver the advantages that many people expect, then why does it exist? The reason is quite simple: in the mid-1990s, a majority of the committee believed that shipping a standard that did not have separate compilation for templates, as C already did for functions, would be incomplete and embarrassing. In short, export was retained in the then-draft Standard on principle.

Principle is very often a good thing. It should never be disparaged, especially by armchair quarterbacks like ourselves (I didn’t start attending committee meetings till the following year), looking back with the benefit of six years’ worth of hindsight.

Remember that, in 1995-1996, templates themselves were still pretty new:

  • The first presentation of the initial C++ template design was made by Bjarne Stroustrup in October 1988 [2].
  • In 1990, Margaret Ellis and Bjarne Stroustrup published The Annotated C++ Reference Manual (the ARM) [3]. The same year, the ISO/ANSI C++ standards committee got going and selected the ARM as its “starting point” base document. The ARM was the first C++ reference to include a description of templates, and they weren’t templates as we know them today; the entire specification and description of these simple templates was only 10 pages long.

At that time, the focus was entirely on enabling parameterized types and functions, the given examples being a List container that could hold different types of objects and a sort that could sort different types of sequences. Even in these early days, however, templates were conceived with the desire for a separate compilation model in mind. Cfront (Stroustrup’s C++ compiler) had support for a form of “separate” template compilation for these simple templates, although its approach was not scalable; see the note in the previous article.

  • During 1990-1996, C++ compiler vendors flourished and took different routes with their template implementations, and at the same time the standards committee greatly enhanced (and complexified) templates. In the latest editions of Stroustrup’s The C++ Programming Language [4], the specification and description of templates occupies 44 (somewhat larger-than-ARM) pages: 27 pages in the body of the book, and 17 pages in the appendices.

In the early and mid-1990s, the committee was principally trying to make templates more robust and practical to support the intended basic uses. Few suspected the enormously flexible and slightly monstrous wonder they had created — it was known that templates were a Turing-complete metalanguage allowing programs of arbitrary complexity to be written that could execute entirely at compile time. But all the modern template metaprogramming and advanced library design that’s in vogue today were largely unanticipated by the people who gave us the very templates that make it possible in the first place, and the techniques were largely unknown during 1990-1996. Remember, it wasn’t until late 1994 that Stepanov made his first presentation of the STL to the committee, which adopted it in 1995 as a groundbreaking achievement — and by today’s standards the STL was “just” a container and algorithm library. Groundbreaking to be sure in 1995, and a powerful differentiator of C++ from other languages still today, but it was nonetheless just the first testing of the template waters by today’s standards.

This is why I say that, “in 1995-1996, templates themselves were still pretty new.” Modern templates in their (mostly) final form existed, but even the people who invented them didn’t fully realize what they were capable of. The global C++ community was much smaller than it is today, few compilers supported more than ARM templates, and most compilers’ template support of any kind was poor or essentially useless. Around that time, only Cfront could cope with the initial STL, for example.

So it was that the community in general and the standards committee in particular still had a comparatively short record of real-world experience with even the simpler ARM templates that existed. The climate in 1996 was no longer quite embryonic, but it was young and still growing and forming.

And it was in this formative climate, with that limited experience, that the standards committee was forced to decide whether to keep exported templates in the then-draft Standard.

1996

In 1996, even with the little information that was available, enough was known that export made a lot of experts nervous. In particular, it made all of the compiler vendors nervous. Even supporters of export viewed it as a necessary compromise, while still disliking export as a source of complexity; some would have preferred general separate compilation with no special keyword.

In July 1996, there was a large coordinated push within the committee against export. In particular, it was argued, the export model had never been implemented, and the committee had no idea whether it would actually work as intended. Several C++ vendors had implemented various forms of template source organization models, but export followed none of them; export was a completely new and experimental beast with no implementation experience behind it. In fact, there were papers presented at that time — papers that in retrospect could be called insightful bordering on prescient — that detailed some of the major potential shortcomings of the export model as described in the draft Standard.

In particular, all of the compiler implementers unanimously opposed export on the grounds that it was too early to know if they were doing the right thing. They had serious unanswered concerns about the existing export formulation, and they didn’t feel they had enough experience yet to come up with a fully baked alternative (not to mention insufficient time; the Standard was being stabilized and would be set in stone the following year, 1997). For those reasons, the compiler vendors unanimously didn’t want to rush a separation model into the first standard (C++98). Rather, they wanted to take time to design it right and do it in the next standard. They favored the idea of separate template compilation in principle, but felt that export wasn’t fully baked, and they still didn’t know enough to do it right.

They lost, narrowly, and export stayed in the Standard [5]. It would be armchair quarterbacking at best to be unduly critical about this outcome, however. As I summarized earlier, a (slim) majority of the committee believed that shipping a standard that did not have some form of “separate” compilation for templates, as C already did for functions, would be incomplete and embarrassing. Several compilers had already been experimenting with forms of “separate” template compilation, and it seemed to be a good idea in principle. In short, export was retained in the then-draft Standard on principle. And it’s a good principle, not to be disparaged.

To emphasize, note that the world’s compiler vendors opposed export in particular and did not oppose the principle of separate template compilation. They just felt they needed more time to be confident that the Standard would get it right. Although some of the world-class experts who in 1996 voted in favor of retaining export now see it as a mistake, the intent and motivation was good. And there is still hope that export will deliver some benefits — if not all the big ones that were initially hoped for — as we gain experience with the first shipping compiler to implement export (Comeau 4.3.0.1 released in August 2002).

Our Export Experience to Date: EDG

As noted last time, the only implementation of export in the world was recently completed by EDG (Edison Design Group) [6], a company of three people who happen to be three of the most respected C++ language implementers on the planet. EDG doesn’t sell C++ compilers; its customers are C++ compiler vendors who integrate the EDG language implementation into their own products. In August 2002, Comeau released the world’s first shipping export-enabled compiler [7].

We can gain much learning from EDG’s experience. EDG reports that, in their experience, export is as difficult to implement as any three other major C++ language features they’ve done (such as namespaces or member templates). The export feature alone took more than three person-years to code and test (not including design); by comparison, implementing the entire Java language took the same three people only two person-years.

Why is export so difficult to implement, and so complex? EDG cites the following major reasons:

  1. export relies on Koenig lookup. Most compilers still get Koenig lookup wrong even within a single translation unit. (Informally, this means a source file.) export requires performing Koenig lookup across translation units.
  2. export requires dealing simultaneously with many symbol tables. Instantiating an exported template can trigger cascaded instantiations in other translation units. Each must be able to refer to entities that existed (or “sort of existed”) when the template definition was parsed. In C++, dealing with one symbol table is complicated enough. With export, at least conceptually you need to simultaneously deal with an arbitrary number of symbol tables.

Along the way, EDG has found and reported numerous places where the C++ Standard does not specify how other C++ features are supposed to work in the presence of export; we’ll consider some examples of these now.

Ch-ch-ch-changes: Export’s Shadow Falls on Existing Language Features

export has a few surprising effects on existing language features. Many of these real effects of export are not mentioned or addressed in the Standard. In particular, export “exports” more than its template:

  • Some functions and objects in unnamed namespaces must now be accessible and callable across translation units, if they are used in exported templates. Similarly, some file-static functions and objects must now have external linkage, or at least behave as though they did, if they are used in exported templates. This is counter to the intent of unnamed namespaces and namespace-scope static, which was to make those names strictly internal to their original translation unit. (File-static functions and objects are deprecated, and you should use the unnamed namespace instead, but they’re still part of Standard C++.)
  • Overload resolution must also be able to resolve names from an arbitrary number of different translation units — including, amusingly, overloading names from an arbitrary number of unnamed namespaces. A major benefit of putting internal functions into the unnamed namespace (and the deprecated file static) was to “privatize” those functions so you could give them simple names without worrying about name conflicts and overloading effects across source files. Now, because part of the protection is being removed and they can and do participate in overload resolution with each other via exported templates, it’s (alas) a good idea to obfuscate their names again if you use such functions or objects in an exported template, even if the function is in an unnamed namespace or file static, so as to avoid silent changes of meaning.
  • There are new ambiguities and potential ODR (One Definition Rule) violations. For example, a class may have multiple befriending entities in different translation units, and declarations of that class from those different translation units may all be participating in an instantiation. If so, which set of access rules should be applied? These issues may seem minor and many of the errors may be innocuous, but on some popular platforms ODR violations are increasingly important (see [8] for one example).

Incidentally, because export is underspecified in these and other issues not addressed in the Standard, EDG had to make decisions on questions with unspecified answers. There is a potential danger that if there are more implementations they will not be perfectly compatible with EDG’s and each other’s semantics.

Export Can Be Difficult to Use Correctly

export will probably be somewhat more difficult to use correctly than normal templates. Here are three examples to illustrate why this is so.

Example 1: It is easier than before for programmers to write programs that have hard-to-predict meaning. Like an inclusion-model template, an exported template commonly has different paths by which it could be instantiated, and each path commonly has a different context. For those who might say, “But we already have that problem with functions defined in header files (e.g., inline),” note that this template problem is already greater than that because there are more opportunities for names to change meaning, in particular because templates use a wider set of names than closed functions do. Templates use dependent names, names that are dependent on (and therefore vary with) the template arguments. So for each instantiation of the template with the very same template arguments, the template’s user must be careful to provide exactly the same context (e.g., overloaded functions that operate on that template argument type) to prevent the instantiation from inadvertently having a different meaning in different files, which would be a classic ODR violation. Why is this expected to be somewhat worse under the export model than for inclusion-model templates? The big thing about export is the fact that, in addition to the above, there are names from multiple translation units available to name lookup, which is not true in any other context in Standard C++.

Example 2: It is harder for the compiler to generate high-quality diagnostics to aid programmers. Template error messages are already notoriously hard to understand because of long and verbose names, but besides that, what’s less obvious to programmers is that it’s already harder for compiler writers to give good error messages for templates because templates can generate multiple and cascading instantiations. With export, there is now the additional dimension of multiple translation units — a message like “error on line X, caused by the instantiation of this function, caused by the instantiation of this function, caused by the instantiation of this function...” must now add which translation unit it was in when it happened, and each line in the traceback could be from a different translation unit. Detecting ODR violations for exported templates is a challenging problem in itself, but detecting what was really meant so as to provide “did you mean” guidance is even harder. Many of us would be happy just to have our compiler emit readable error messages for plain old templates.

Example 3: export puts new constraints on the build environment. The build environment does not consist of just .cpp and .h files any more, and many of today’s tools don’t understand how to handle apparently circular dependencies when the linker can go back and change .obj (or .o) files. As noted in the previous article, if you change an exported template file, you need to recompile that, but you also need to recompile all the instantiations. That is, export really does not separate dependencies; it just hides them. As also noted in the previous article, for EDG’s implementation at least there’s also a reverse dependency. Even experts find it hard to accept that export really is like this, but it is.

As the world’s top template guru, John Spicer of EDG, notes: “export is intricate in nature and it takes a lot of work to understand the consequences. It’s hard to make up simple usage guidelines that will keep users out of trouble.” [Emphasis mine.]

Potential Benefits of Export

Now that an implementation of export is finally available, for the first time ever, the time is ripe for the early adopters in the C++ community to start kicking the tires and see how it runs in the field. Here are two actual and potential values of export that some early adopters hope to achieve:

  1. Build speed (still). Although EDG’s expectation is that export will give the same or poorer build speed in most cases, many people still hope that this fear will turn out to be unfounded, or at least that export may improve build speed in certain cases. Exploration in this area will let us discover how common those cases are and how easy or difficult those cases are to construct. In particular, it is hoped that translation units that use exported templates will be less sensitive to (i.e., less costly to rebuild when there are) changes in the template’s definition.
  2. Caveats to #1: For reasons why being able to break dependencies may not be the case and why dependencies still exist, see the previous article. Also, note that EDG says that, in their implementation of templates, this potential advantage is available equally to both inclusion and export source organization models — which would mean that for EDG at least (which is the only available export implementation today) export would have no benefit over inclusion-model templates.

  3. Macro leakage. Macros leak across traditional inclusion-model header files. Because the inclusion-model source code is entirely available in each translation unit, outside macros pulled in from elsewhere earlier in that translation unit can affect the template’s definition. With export, macros don’t leak across translation units, and this will help the template author to maintain better control over his template definitions (which are off in a separate file) and prevent outside macros from as easily interfering with his template definitions’ internals.
  4. Caveat to #2: Note, however, that within the C++ standards committee’s early work on the next Standard (C++0x), the Evolution Working Group is already pursuing better and more general solutions to the macro problem in all contexts, such as Stroustrup’s work on potential new #scope and #endscope preprocessor extensions. If such a solution is adopted, it would eliminate entirely this advantage of export, because the preprocessor scope control solution would deliver all the macro-protection benefits of export, and many more, in a better way.

In summary, it remains to be seen in the coming months and years how much benefit export gives over normal “include all the code in the header” templates, but I’d like to strongly encourage the people who run those tests to also report the results of organizing their code to take full advantage of EDG’s non-export capabilities and see whether any advantages to export actually remain.

Morals

So should you use export, and if so, how can you use it safely?

Well, for the next year or more, only a fraction of C++ programmers will be using an export-capable compiler that they can experiment with. For most C++ programmers, then, the question of whether to use export is moot: they can’t, not anytime soon, so they won’t.

What if you’re using one of those up-and-coming newfangled export-capable compilers? Ah, now we can finally come up with an initial guideline:

Guideline: For portable code, don’t use export.

export certainly can’t be used for portable code, because given today’s meager compiler support any code that uses export is not portable in practice today and will not be portable for some time to come. Even if and when other non-EDG-based implementations eventually become available, they might not be fully source compatible with EDG’s implementation because EDG had to make decisions somewhat on the fly about how export should behave in areas where the Standard was silent. (Many of these are under consideration for inclusion in the Standard, however, as EDG has reported the issues and their own decisions. If those clarifications are included in the Standard, it would help to mitigate this danger.)

What if you don’t need portable code, have export, and are tempted to use it? Then caveat emptor: be aware that export is still experimental, that it does not necessarily deliver the benefits people expect, and that it adds some new operational wrinkles to existing C++ language features. Be aware that exported templates can also be trickier to write for the reasons mentioned in these two articles and summarized again below.

My best advice today would be that, even if you just use one compiler and it has export today, in general you should try to avoid export for now in production code because it is still an experimental design. Let someone else be the guinea pig as we spend the next year or two trying it out and learning about what export will really give us.

Guideline (For Now): Avoid export.

But, if you do decide to be one of the early-adopter experimenters, here are some things we already know you can do to make life safer and less stressful.

Guidelines: If you do choose to use export selectively for some templates, then:

  • Don’t expect that export means you don’t have to ship source code (or its equivalent) anyway. You still do, and this will not change.
  • Don’t expect that export means your builds will be earth-shatteringly faster. Initial experience is inconclusive, but your builds could well be slower.
  • Check that your tools and environment can handle the new build requirements and dependencies (e.g., make sure all your tools understand that the linker can change its input .obj/.o files).
  • If your exported template uses any functions or objects that are in an unnamed namespace or file static:
    • Understand that those functions/objects will behave as though they were extern, and that the functions are liable to participate in overload resolution with an arbitrary number of functions in other unnamed namespaces from an arbitrary number of source files.
    • Always obfuscate (uglify) the names of those functions so as to prevent unintended semantic changes. (This is a pity because the unnamed namespace and file static are supposed to protect you from this so you don’t have to obfuscate the names, but if you use export you can too easily silently lose this protection and should obfuscate them again.)
  • Do understand that this is not a complete list and that you will probably encounter some other issues beyond the ones we already know about for today’s normal template uses. As Spicer put it: “It’s hard to make up simple guidelines that will keep users out of trouble.” Do understand that export is still somewhat experimental, and that as a community we haven’t yet had a chance to learn how to use export, so we don’t have a complete set of good safety and usage guidelines yet. This will likely change in the future.

It’s too early too tell whether the “avoid export” guideline will turn into permanent advice or not. Time and experimentation will tell. As vendors slowly begin to adopt and support export in the coming years, and the community gets a chance to finally try it out, we’ll know much more about how and when to use it — or not.

Acknowledgments

Many thanks to Bjarne Stroustrup, Steve Adamczyk, John Spicer, and Daveed Vandevoorde for their comments on drafts of this material.

References

[1] H. Sutter. “Sutter’s Mill: ‘Export’ Restrictions, Part 1,” C/C++ Users Journal, September 2002.

[2] B. Stroustrup. “Parameterized Types for C++,” Proc. USENIX Conference, Denver, October 1988.

[3] M. Ellis and B. Stroustrup. The Annotated C++ Reference Manual (Addison-Wesley, 1990).

[4] B. Stroustrup. The C++ Programming Language, 3rd ed. and special ed. (Addison-Wesley, 1997 and 2000).

[5] Things were quite turbulent and support seesawed back and forth, balanced on a fulcrum. At the March 1996 meeting, the straw vote was two-to-one against separate template compilation. At the July 1996 meeting where the export keyword was introduced, the vote was two-to-one in favor of export.

[6] See <www.edg.com>.

[7] See <www.comeaucomputing.com>.

[8] H. Sutter. “Standard C++ Meets Managed C++,” C/C++ Users Journal, C++ .NET Solutions Supplement, September 2002.

Herb Sutter (<www.gotw.ca>) is convener of the ISO C++ standards committee, author of the acclaimed books Exceptional C++ and More Exceptional C++, and one of the instructors of The C++ Seminar (<www.gotw.ca/cpp_seminar>). In addition to his independent writing and consulting, he is also C++ community liaison for Microsoft.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video