
"Sebastian Redl" <sebastian.redl@getdesigned.at> wrote in message news:447745E2.6040503@getdesigned.at...
Maarten Kronenburg wrote:
Thanks for your comments. When an implementation is very slow, it will not be useful at all, and not be used.
If a compiler ships with a slow standard library, there will be pressure on the vendor to make a better library. There is no need to specify complexity bounds that a vendor will do his best to reach anyway. I think the way integer is specified should follow the guidelines of how string is specified, as both try to act like a built-in type. There are no complexity bounds on, say, string::find_first_of, no requirement that it be O(N+M), where N is the string's length and M the number of characters being searched for. Yet it is certainly possible to implement it this way, instead of the naive O(N*M). If the library vendor is smart and there's a need for speed, it will be implemented in a good way. If there are tradeoffs between different functions, different libraries will offer different tradeoffs or a library will have a compile-time switch to choose. However, by specifying the complexity of a given function, you're limiting the implementer. This is particularly significant in a library full of possibly complex algorithms such as integer.
Thanks for your comments. When I relax the complexities then they will say that I allowed for too slow implementations. Let's keep the complexities in, and at least in my implementation they are fulfilled.
Derivation is a part of C++, so in my opinion users must be able to derive from class integer to make an integer with special properties. How would you explain to a user that whatever he/she does with integer, derivation is not an option, because the destructor does not happen to be virtual. This is what it boils down to in the end.
Well, how do you explain it to users of std::string? The class has been quite happy without virtual destructors.
Then I ask you how to add two strings with different allocators.
Once again I argue that an unsigned integer is an integer, and a modular integer is an integer.
You are wrong here. Of course, given the abstract concept of an integer, an unsigned integer IS-A integer - just as a signed integer is. However, the integer you specify is not an abstract concept, but a concrete class - misnamed, actually, because it's a signed integer. (In the same way as int is assumed to be signed by default.) You can either make integer abstract and derive signed_integer and unsigned_integer from it. Or you can make it concrete, follow the core language in the implicit assumption that it is actually a signed integer, and not have the IS-A relationship between unsigned_integer and integer, thus not deriving one from the other. After all, an unsigned integer IS-NOT-A signed integer.
For an abstract base class, derivation is mandatory. This is not what I want, because I want non-demanding users to be able to use the class integer without derivation. On the other hand I also want demanding users to be able to use derivation for defining integers with specific properties such as an unsigned_integer or a modular_integer, and use mixed expressions. When I make the class concrete then users will never be able to derive from it, unless they make a new class with an integer data member and start all over again. I have thought this through, and I think the way it is, it serves both non-demanding and demanding users best with flexibility if needed.
An unsigned int is actually a modular int with modulus 2^32. When an integer with a certain allocator must be used in an expression with an integer with another allocator, then run-time polymorphism is the only option.
Where did the allocator come into this discussion? Anyway, compile-time polymorphism (i.e. templates) is also an option - make all binary or higher-order operations that work on integers templated on the allocators of all operands, put the burden of merging the allocation correctly on the implementers and specify which allocator is used by the result. Or do it as std::string does and simply don't allow cross-use of different allocator types. (This lack of interoperability has often been cited as a shortcoming, though. The solution here is to make the allocator runtime-polymorphic, not the integer.)
If I don't allow cross-use of different allocators, then no mixed expressions are possible. Why should the use of a different allocator for an integer prevent it from being added to an integer with another allocator? The integer needs to be run-time polymorphic for the same reason: why should it not be possible to add an unsigned integer to a signed integer?
The integer as specified will strictly mimic the int. As mentioned to others I will add a unspecified-bool conversion to be able to use if( x ) with x an integer. But making a "basic" integer and then making it a data member of a "full" integer, I do not recommend that strategy. I recommend to make the interface right and complete from the beginning.
Like std::string did? You can't make everyone happy with any interface, but a minimal interface can at least claim a philosophy.
In my opinion the current design serves both non-demanding users (they can just use the class integer) and demanding users (they can derive from class integer, use an allocator, and use mixed expressions). Then (I hope) as many people as possible will be happy.
What C++ lacks here, IMO, is some sort of mixin concept - effectively a way to call free functions as if they were members of a class.
In my opinion C++ offers so many options that we have these useful discussions.
Sebastian Redl _______________________________________________ Unsubscribe & other changes: