Bobby answers several questions from the fringes of valid C++.
Copyright © 2000 Robert H. Schmidt
I just returned from a week in sunny Orlando, attending Microsoft's Professional Developers Conference (or PDC). Even though I work for Microsoft now, and already knew much of what was going to be announced, I still learned so much new stuff there that I ended up taking 40 pages of notes.
Just before PDC, Microsoft disclosed their development of the C# language. They also disclosed plans to submit C# to ECMA TC39 for consideration as a language standard. I plan to be an observer on TC39, incorporating what I learn into my MSDN writing. This will my first active participation in a standards process, and I'm very much looking forward to the experience.
During PDC, Microsoft finally disclosed the execution environment for C# and its brethren: the .NET common language runtime. This runtime provides a common programming model and base class set to all languages, not just Microsoft's. At the show, I saw demonstrations of languages as diverse as APL, COBOL, Eiffel, Python, and Scheme all interoperating with one another in the common environment.
For the C# standardization to make sense, ECMA will also have to standardize an abstract machine or execution environment for C#. I'm guessing that environment will be based on a subset of the .NET runtime. If I'm right, then some aspects of that runtime will be migratory to other (non-Microsoft) platforms.
The potential result: maximal language interoperability across multiple platforms. Vendors of both niche languages and mainstream languages will be on a level playing field. As I mentioned in my first C# column for MSDN, this could mark the end of the C-family hegemony. (But hopefully not the end of CUJ!)
This Tape Will Self Destruct
Q
Hi Bobby.
When does it ever make sense to commit suicide with delete this? In particular: after you self-delete the current object from within some non-static member function, is it legal C++ to keep doing some work in that function (provided it doesn't access any non-static member)?
Thanks! Patrick Rabau
A
Does it ever make sense? Yes, barely. Is it "legal" C++? Yes, with restrictions.
Instanced member functions are not magic. Deleting this within such a function is tantamount to deleting the same object in a non-instanced or non-member function. Consider
class X { public: void f1() { delete this; i = 1; // oops this->i = 1; // same } static void f2(X *const this_) { delete this_; this_->i = 1; // oops } int i; }; void f3(X *const this_) { delete this_; this_->i = 1; // oops } int main() { X *x = new X; x->f1(); x->f2(x); // same net effect f3(x); // same net effect }All three functions have the same net effect. The only tangible difference: in f1 the pointer is implicitly named this and (conceptually) passed as a hidden parameter, while in f2 and f3 the pointer is explicitly named this_ and explicitly passed as a parameter.
In each case, the object is dead. That the death was suicide rather than murder does not change the post-mortem conditions. So restrictions after delete this really generalize to restrictions after any delete expression:
- The delete expression's operand must have been created via non-placement non-array new.
- Nobody can reference the deleted object or its address. This implies no member accesses through the deleted this pointer.
- You can't delete the object twice.
Having written all that, I do want to highlight an aspect unique to delete this.
By executing delete this, an object assumes how it was born, and that users will know when it has died. (Since users do not evaluate delete directly, they have no overt evidence of the destruction.) In effect, these assumptions becomes part of the object's contract requirements. Such a contract severely restricts user choice and presumes user discipline. You can mitigate this last point some by hiding constructors and destructors, and presenting alternative object creation/destruction functions (where the destruction function presumably calls delete this).
Alternatively, you could instrument an object to know how it was allocated, so that it evaluated delete this only if was newed. For example, a hidden tag could encode the construction path, and be queried for the proper destruction path later. But if you're going to that trouble, you should consider making the class a template, following the spirit (if not the implementation) of STL allocators.
Partial Specialization
Q
Hi,
I hope the enclosed short program explains my problem. Are really none of the MODEs 2, 3 and 4 legal C++? I tested it with MSVC++6.0, and it did only accept MODE 1. Achim Kupferoth
A
I've simplified your program while maintaining its essence. My version appears as Listing 1.
I am able to reproduce your results with all of my compilers: MODE 1 compiles as desired, while MODEs 2 through 4 fail to compile.
What you're attempting is partial specialization of a function template, something the C++ Standard does not allow. With a conforming compiler, you can get the same effect by partially specializing the class template owning the function:
// // primary X // template<typename T> struct X { static void f(); }; // // partially specialized X // template<typename T> struct X< Y<T> > { static void f(); };Now the call
X< Y<int> >::f();will reference f from the partially specialized class. You can verify this by declaring that f as private, then recompiling you should get a diagnostic about f being inaccessible.
As I say, this code will work on a conforming compiler. Unfortunately for you, Visual C++ does not properly support partial specialization of class templates. If you try to compile the two X definitions I've shown, you'll see two diagnostics:
'X<class Y<T> >' : template class has already been defined as a non-template class unrecognizable template declaration/definitionSorry, but I can't think of a work-around that is any better than your original MODE 1 solution.
Ambiguity Rides Again
Q
This is most probably a simple question for you. I was surprised that I did not find anything in the usual FAQs but this might be because I do not exactly know the name of the problem.
Now the problem: why does the following program not compile?
class A { public: A(int); }; class B { public: B(A const &); void f(); }; int main() { int val = 7; B b1(A(7)); B b2(A(val)); b1.f(); b2.f(); // error! };I only have two compilers to check this code. Sun's CC 5.0 emits the following error message:
B(*)(A) is not a structure type.g++ version 2.8.1 gives this error message:
request for member 'f' in 'b2(A)', which is of non-aggregate type 'B ()(A)'I can't figure out the obviously fundamental difference in instantiating b1 and b2. Since two compilers complained similarly, I suppose this is not a compiler bug. But where in the C++ Standard lies this rule buried and why?
Thanks much in advance! Cristoph Wolf
A
What you have found is not a compiler bug. Your translators are actually trying to explain the problem through their diagnostics: b2 is not a structure or aggregate. Put another way, b2 is not an object that can be dereferenced as b2.f().
If that hint doesn't help, try adding these two lines to the end of main:
b2(val); b2(A(val));You should find that these lines compile. This fact suggests both that something named b2 exists, and that b2 can take arguments of type int or type A.
Still give up? Here's the answer: b2 is a function, not an object. That's why your translators are complaining the expression b2.f() is attempting to reference the member f of function b2, and functions don't have members.
I can anticipate your next question: why is b2 a function, especially since its declaration looks so much like that of b1? The answer is one that I've discussed before, but which bears brief repeating here.
What you've hit is a reasonably well-known ambiguity within the C++ grammar. In essence, the statement
B b2(A(val));can be interpreted as the declaration of either
- an object b2 of type B directly initialized with the value A(val), or
- a function b2 returning B and accepting a parameter named val of type A, where the parameter name val is surrounded by redundant ().
While you clearly want your translators to pick the first interpretation, evidence suggests that they pick the second. The C++ Standard supports the translators' choice in subclause 8.2/1 (edited for brevity):
The choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. The resolution is to consider any construct that could possibly be a declaration a declaration.In the current ambiguity, A(val) can be interpreted either as a the declaration of the parameter val, or as a function-style cast of val to type A. According to the disambiguity rule in 8.2, the compiler interprets A(val) as a declaration, which forces interpretation of b2 as a function.
You can remove the ambiguity, and force b2 to be an object, by changing its declarations to any one of
B b2((A) val); B b2 = A(val); B b2(val); B b2 = val;Erratica
Several Diligent Readers report trouble with Carlo Pescio's latest LENGTHOF variation [1]:
template<int N> struct Sized { typedef char x[N]; }; template<class T, int N> typename Sized<N>::x &LengthofHelper(T (&)[N]); #define LENGTHOF(x) \ sizeof(LengthofHelper(x))In particular, these readers can't get Carlo's solution to compile with (drum roll please) Visual C++ version 6! As one reader noted, since I work for Microsoft again, I really ought to test my code against Microsoft's compiler and make those test results known.
Hmmm. I could have sworn that I tested Carlo's code against that compiler. (Actually, I could have sworn that I've tested just about everything against that compiler, since you-all love it so.) But I apparently suffered a lapse here, for when I compile the code under Visual C++, the compiler laughs back with the too-familiar
reference to a zero-sized array is illegal reference to a non-constant bounded array is illegalI haven't gone digging in the Visual C++ bug database to discover the precise problem here; but evidence suggests that the compiler simply can't use a template parameter to dimension an array. Since this is such a frequently-encountered problem, I have to believe we will fix it in Visual C++.NET. I'll contact the compiler people and let you know what I find.
Note
[1] Originally published in my July 2000 column.
Bobby Schmidt is a freelance writer, teacher, consultant, and programmer. He is also an alumnus of Microsoft, a speaker at the Software Development and Embedded Systems Conferences, and an original "associate" of (Dan) Saks & Associates. In other 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.