Teaching new tricks to an old dog (C++ -->Ada)

  • Thread starter Turamnvia Suouriviaskimatta
  • Start date
J

Jerry Coffin

Martin said:
That 'problem' isn't unique to Ada - I can't think of another
language that *does* define a pre-processor.

A preprocessor per so, no. The type of capabilities is a different
story. For example, Lisp includes macros. Most assemblers include (much
more extensive) macros as well as an eqiuvalent of C's
#if/#ifdef/#ifndef/#endif. Literal defininitons in PL/M provided some
of the same capabilities as C macros. I could go on for a while, but
you get the general idea -- the general capabilities aren't entirely
unique to C and C++, but are almost entirely missing from Ada.
And if you like a preprocessor - just use one! You can always write
a makefile/.bat/.com/<whatever> to invoke the preprocessor before the
compiler is called. GNAT comes with an Ada-centric preprocessor but
there is nothing to stop you using the 'C' one.

This merely changes the problem rather than solving it. Many typical
uses of the preprocessor in C are to deal with portability issues, but
you're asking that they be dealt with by adding yet another program to
be ported.

Even ignoring that, the system is essentially unusable. C and C++
compilers (admittedly, more or less out of necessity) provide
directives so the preprocessor can specify which line in the original
source code resulted in a particular line that was given to the
compiler. This allows the compiler to refer error messages back to the
correct line in the original source code quite easily, and the
compilers do so quite dependably. TTBOMK, Ada compilers don't
(normally) support such directives, rendering the system impractical at
best because error messages are essentially always incorrect.

[ ... ]
See Ada.Exceptions - perhaps not as elegant but it does the job and
back when the first exception mechanism for Ada was designed, I doubt
there was much call for anything other than a 'this fault has
occured' style exception. At the time, I'd guess that 90%+ of all
code in exsistance was COBOL, C or FORTRAN - none of which had any
exception handling!

Having no exceptions at all means you report errors in some other
fashion, and you're free to design one that's as expressive as you
need. The exception handling in Ada is inadequate to the job (at least
IME), but its presence makes it virtually impossible to convince
anybody to accept anything else either.

In many cases, having a feature in an inadequate form is really _worse_
than not having it at all, even if the basic idea of the feature is a
good one. Having the feature rules out alternatives (in may people's
minds), even when they're really superior.
True for Ada83 but not so in Ada95 - again see Ada.Exceptions and
getting expanded again in Ada2005.

This clearly allows more information to be transmitted, which I'd
consider an improvement. Perhaps I'm missing something, but I still
don't see a provision for anything equivalent to catching a reference
to a base class in C++, at least as of Ada 95. Is this being added in
Ada 2005, or have I mis-read things?
Almost certainly backwards compatibility - but I'm sure Bob and/or
Randy could shed more light on that...

That certainly seems like the obvious guess. In the end, it may explain
the situation, but doesn't really improve it. The same, of course,
applies to C++, which clearly makes some sacrifices to maintain a high
degree of compatibility with C. At the same time, I have to say that
IMO, C++ gains more (e.g. C has a much larger user base) while
sacrificing less (C compatibility has produced minor annoyances into
C++, but IMO Ada's exception handling is sufficiently broken that it
seriously detracts from the language).
 
I

Ioannis Vranos

Dr. Adrian Wrigley said:
UpdateString takes an array indexed by integers, updating bottom
and top halves recursively in some obscure way.

OK.



The point is that the input parameter is an array with upper and lower
bounds which are arbitrary, and are different at each level of
recursion.


Which provides no real benefit at least in this function.

I have created the arguments to the recursive call
by slicing the input parameter into two half arrays, with the
help of the array attributes ('First, 'Last, 'Length). Normally,
this takes place without any copying. No heap allocation is
required, and any accesses outside the intended array range for
updating at each level of recursion are excepted.

The most obvious C++ implementation is with vectors, but I think
you need three additional parameters and several checks to get
equivalent semantics. The three new parameters might be
'StartElement' 'EndElement' and 'BaseOffset'. You need the
BaseOffset to accommodate the vectors always starting at 0,
so you can find the problem-domain start.

You may get into problems if the procedure needs to call
other functions that take a vector, if they are unable to
take upper and lower limits to process. Can you make a
vector which is a slice of another vector without copying
all the elements in and out?

This kind of recursive slicing of arrays is something I
have found *much* more natural and reliable in Ada than in C,
where hacking with pointer arithmetic and extra parameters
would be the usual solution.


One can use two iterators while passing the length as an extra function
parameter.

Besides that, nothing else changes.
 
J

Jerry Coffin

Robert said:
*Some* Ada fans have used that argument, but I'm an Ada fan, and I
don't agree with it. I happen to think that (other things being
equal) it's better to put functionality into libraries than to build
them into the syntax and semantics of the language.

You're right. I did not intend to put words in your mouth (or anybody
else's for that matter). What I said was wrong, and I apologize.
 
M

Matthew Heaney

Ioannis Vranos said:
I think an associative container like map fits better to this. What do
you do in Ada if you want to associate product names with prices, in
the style:

productlist["something"]= 71.2;

Ada 2005 comes with a standard container library, very similar to the
C++ STL. For example:

declare
package Map_Types is
new Ada.Containers.Indefinite_Ordered_Maps
(String,
Float);

M : Map_Types.Map;
begin
M.Include (Key => "something", New_Item => 71.2);
end;

There's also a standard hashed map.

or a name with a number (string with string) in an address book
application for example?


namelist["Obry Pascal"]="321-45563";

See above. The element type in this case is String instead of Float.

Also may you tell me if that famous compile-time boundary checking
applies (can be used) to user-defined containers too?

I guess the answer is yes, since subtype constraints are inherited from
generic actual types. For example, if we have:

type My_Float is new Float range 0.0 .. 42.0;

and you instantiate the map with My_Float, then you'll get
Constraint_Error if you attempt to insert a float value outside the
range of the subtype.

-Matt
 
J

Jim Rogers

If it is also defined in std namespace, I am going to smile.

Ada does not have anything called std namespace.

Ada packages provide encapsulation and namespaces.
Ada does provide a package named Standard, but that is where
fundamental data types are defined such as Standard.Integer.

No containers are defined in Standard. There is no need for
this in Ada.

Jim Rogers
 
M

Matthew Heaney

Ioannis Vranos said:
BTW does Ada provide dynamic arrays like vector/deque?

If you know the capacity at the time of declaration, then you can just
use an array. That has always been true since Ada83.

Otherwise you can use the new vector container, which is basically the
same as the C++ std::vector.

-Matt
 
J

Jerry Coffin

Robert A Duff wrote:

[ ... ]
By the way, *most* of the work a compiler does to optimize away
checks is the same as for other optimizations (despite what some
Ada fans have claimed in this thread), so optimizing away checks is
not as costly as you seem to think (in terms of compiler-writer's
effort).

First of all, I'm not saying the cost is necessarily particularly high,
only that _very_ few things are free, even when the cost isn't
necessarily obvious.

Second, as far as the optimization itself goes, it should be basically
constant folding followed dead-code elimination. It's certainly true
that most optimizing compilers are already likely to include both, and
it's also true that techniques for both have been well-known for some
time (I'm pretty sure the Dragon book includes examples of each).
Nonetheless, while doing them to at least some extent is well-known,
each is typically open to at least some improvement. At least to me, it
appears that producing efficient output from Ada source code depends
more upon them being done well than is the case for most other
languages.
Sorry, but if you think a class is called a tagged record in Ada,
you don't understand the language.

I'll openly admit that my knowledge of Ada 95 is _extremely_ limited
(I'm afraid I quit using Ada before 1995). Perhaps I need to take
another look in this area.

OTOH, doing a bit more looking, if I've misunderstood the situation, at
least I have some company. For example:

http://en.wikibooks.org/wiki/Programming:Ada:Types:record

claims that "The tagged record is what in other languages is called a
class."

Likewise:

http://www.adaic.com/docs/95style/html/sec_9/9-2-1.html

while less explicit about the equivalence, makes statements that
certainly make a tagged record look and sound a great deal like a class
in C++.

Perhaps you can provide a better explanation?
 
J

Jim Rogers

One Ada feature that cannot be implemented through any container library
is the ability to define a range-restricted floating point type. You can
do this in C++, but not through the use of a template. Templates cannot
take floating point values as parameters.

For instance, in Ada it is trivial to define a floating point type with
10 decimal digits of precision and all values in the range of 0.0 through
1.0.

This sort of type is very useful for defining normalized values.

type Normalized is digits 10 range 0.0..1.0;

I used this technique for robotic control systems. The value read
from a joystick indicating a turn could be normalized, sent across
an RF link to the robotic vehicle, and then converted to the dynamic
range available to the target actuator. The joystick could employ a
14-bit a/d converter, or it could employ a 10-bit a/d converter. The
actuator never saw a difference. The joystick output was decoupled
from the actuator input while providing excellent control
capabilities.

The compiler could ensure that all normalized values were within
the required range of 0.0 through 1.0. This allowed me to eliminate
explicit error checking in my code while retaining data correctness.

Jim Rogers
 
G

Georg Bauhaus

I am not sure what you mean with the above, perhaps to use the
facilities of <ctime> to measure their performance? Of course vector is
faster than map.

You say "of course" now? I don't really understand then
why you asked me why my Ada _array_ code is faster than your
C++ std::_map_ code.
You mean it can use signed indices?

Not at all. I mean that C# arrays can be dynamic or fixed size.
They always start at 0, IIRC.
Look, just to be technically
accurate, C++ can also use negative indices,

Actually, there is no negative index in your example.
There are some offset computations, resulting in index
values no less than 0.
By the same argument I could say that a 10-element
C-array can be indexed by the number 100000 by first
decrementing the pointer and then adding 100000 as an
offset. But you do not get the element number 100000,
because there is no such element.

Index types are conceptually different from offsets from
the first element.

As a clever trick, this is nice though. I like the power
of assembly think.

vector<int>::iterator pi= vec.begin()+4;

// Makes vec[2]== 4.
pi[-2]= 4;
OK. And lets see what happens when the C99 arrays will be adopted.


Do you mean C99's built in Variable Length Arrays (VLAs)? I do not think
they will be adopted in C++.

Let's see.

Georg
 
M

Matthew Heaney

Ioannis Vranos said:
inline int doubleF(const int &arg) { return 2*arg; }

vector<int>someArray(10, 1);

transform( someArray.begin(), someArray.end(),
someArray.begin(), doubleF );

You can do this in Ada something like:

declare
V : Integer_Vectors.Vector := To_Vector (1, 10);

procedure Double_Arg (C : Cursor) is
procedure Process (I : in out Integer) is
begin
I := 2 * I;
end;
begin
Update_Element (C, Process'Access);
end;
begin
V.Iterate (Double_Arg'Access);
end;
 
I

Ioannis Vranos

Ioannis said:
One can use two iterators while passing the length as an extra function
parameter.


On second thought, I think no extra parameter is required. I think this
qualifies as the C++ equivalent:


#include <vector>

typedef std::vector<int>::iterator iter;

void UpdateString(const iter iFirst, const iter iSecond, int K)
{
const int SIZE= iSecond-iFirst;

if(SIZE== 0)
return;

else if(SIZE==1)
{
*iSecond+=K;
return;
}

else
{
UpdateString(iFirst, iFirst+ SIZE/2 -1, 19);
UpdateString(iFirst+ SIZE/2, iSecond, 27);
return;
}
}


int main()
{
using std::vector;

vector<int> vec(10);

// Pass a subrange
UpdateString(vec.begin(), vec.begin()+4, 0);
}
 
M

Matthew Heaney

Jim Rogers said:
No containers are defined in Standard.

I think the language model is that library-level declarations are
"logically" nested within package Standard, so you could make an
argument that the containers are indeed "defined in Standard."

There is no need for this in Ada.

Hmmm... not sure what you mean. There certainly is a need to partition
the global namespace, which is why we have the Ada, System, Interface,
etc, package hierarchies. This is no different from what C++ does.
 
I

Ioannis Vranos

Jim said:
One Ada feature that cannot be implemented through any container library
is the ability to define a range-restricted floating point type. You can
do this in C++, but not through the use of a template. Templates cannot
take floating point values as parameters.

For instance, in Ada it is trivial to define a floating point type with
10 decimal digits of precision and all values in the range of 0.0 through
1.0.

This sort of type is very useful for defining normalized values.

type Normalized is digits 10 range 0.0..1.0;

I used this technique for robotic control systems. The value read
from a joystick indicating a turn could be normalized, sent across
an RF link to the robotic vehicle, and then converted to the dynamic
range available to the target actuator. The joystick could employ a
14-bit a/d converter, or it could employ a 10-bit a/d converter. The
actuator never saw a difference. The joystick output was decoupled
from the actuator input while providing excellent control
capabilities.

The compiler could ensure that all normalized values were within
the required range of 0.0 through 1.0. This allowed me to eliminate
explicit error checking in my code while retaining data correctness.



There are C++ libraries that provide such functionality. I suppose one
of them is GMP:

http://www.swox.com/gmp


http://www.swox.com/gmp/manual/Floating-point-Functions.html#Floating-point Functions
 
J

Jim Rogers

Regardless of the wording, however, I think this is (mostly) a
red-herring. What do you consider unsafe about (for example) some chunk
of code being compiled only when I want it to be? If I really believe
in the compiler's optimizer, I can already do things like "if False" in
Ada, and that code clearly won't ever execute. It just happens that C
and C++ provide a simple and practical method of doing the same things
in a way that's easy to externally control.

The kinds of things that were considered unsafe with a pre-processor are
those things that are not type-safe. These problems sometimes arise in
the use of macros.

For instance the following macro can cause some serious problems when
mis-applied:

#define SWAP(A,B) ((temp) = (A);(A) = (B); (B) = (temp))

char s1[30];
int i;

SWAP(S1, i);

In many cases it is safer to define an in-line function than to
define a macro.

Jim Rogers
 
W

Wes Groleau

There are lots of good uses for multiple inheritance. However,
there are also lots of dangers in it. Ada correctly comes down
on the side of safety, but I do sometimes wish it were available.

One thing I've never quite understood is the argument of ambiguity.
Ada allows other things that have a potential of ambiguity. The
solution there is that when something _actually_ has two meanings
(as opposed to _potentially_) then you get neither.

That is the approach perl takes to multiple inheritance. At least
I think so--I haven't finished studying it. What does C++ or Eiffel
do when multiple inheritance creates two meanings for the same name?
 
M

Matthew Heaney

Ioannis Vranos said:
Also the lack of a "STL like" library in Ada until now probably sounds
like that Ada lacks much of the high-level abstraction that C++
provides.

It had nothing to do with language features. Except for a few little
things (mostly having to do with syntax), the Ada 2005 standard
container library could have been written in Ada95 or Ada83.
 
I

Ioannis Vranos

Georg said:
You say "of course" now? I don't really understand then
why you asked me why my Ada _array_ code is faster than your
C++ std::_map_ code.


I suppose I did not notice that you were comparing with std::map at that
point. Direct comparison of an Ada fixed size array should be vs vector
or better, valarray.

Actually, there is no negative index in your example.
There are some offset computations, resulting in index
values no less than 0.
By the same argument I could say that a 10-element
C-array can be indexed by the number 100000 by first
decrementing the pointer and then adding 100000 as an
offset. But you do not get the element number 100000,
because there is no such element.


Actually this example produces undefined behaviour, since valid
behaviour is to point/access within the sequence or point (and not
access) one past the end element of the sequence.


But in a case where


vector<int> vec(10);

vector<int>::iterator pi= vec.begin()+4;

// ...

pi[-2]= 4;


it accesses an actual object (there is an actual object) there.


I can not understand why in an array with index range [-200, -100] there
is an object in -199 "more" than the example above.


Index types are conceptually different from offsets from
the first element.


In C++, index is always (has the notion of) an offset.

As a clever trick, this is nice though. I like the power
of assembly think.


Yes I think it's cool too. :)


Not to mention that we can treat any part of a built in array or an
object as a sequence of unsigned chars (bytes).
 
J

Jim Rogers

There are C++ libraries that provide such functionality. I suppose one
of them is GMP:

http://www.swox.com/gmp


http://www.swox.com/gmp/manual/Floating-point-Functions.html#Floating-p
oint%20Functions

My reading of the information at that url indicates that GMP allows
the specification of precision, but not the specification of a limited
range of valid values.

The Ada example above specifies a precision of 10 digits and also
specifies a range of valid values from 0.0 through 1.0.

Jim Rogers
 
W

Wes Groleau

if we choose to use a cryptic form of C++, with all the little
shortcuts that make the code less readable, we might achieve
some reduction in the KSLOC, but at what cost in understandability?

And at what cost in development time? If the reader can't
understand the code, chances are the writer doesn't understand it
as well as he thinks he does (or she).

"If it ain't broke, don't fix it."

"With all due respect, sir, if nobody understands it, it's broke."
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,202
Messages
2,571,057
Members
47,665
Latest member
salkete

Latest Threads

Top