Ok, I have a very theoretic question here, on how I would design a class, which is part of a class library.
Let me first explain about the library.
It's a library of string classes. Every string class provides the same interface as std::string, with a few additional functions for interoperatibility. What makes this library special is that all classes can be used interchangeably in, say, function arguments, without having any virtual functions or base classes in the actual classes. The implementation of this is regards the question I have.
The string classes provide the same interface with different storage semantics. For example, simple_string is the kind of dynamic string that one learns to implement in a basic programming class. safe_string would be the same, but with careful bounds checking on every operation. refcount_string would be a reference counted copy-on-write string.
Every class should be fully conformant to the C++ standard for string classes.
The key to the exchangeability is a special group of classes, called any_string, any_string_ref and any_string_const_ref. I believe that, with a good compiler, my implementation of these classes would be exactly as fast as a common base class for all string classes, i.e. one virtual call for every function call through the holder classes.
any_string works much like the Boost.Any holder, but works only for classes that provide the std::string interface. It holds one object of any such class.
any_string_ref and any_string_const_ref do the same, but with slightly different semantics.
And here's my problem.
any_string_*ref should act like a reference to a hypothetical base class. That is, you assign one object at construction time, and it will forever reference that object. It will actually reference that object, not allocate a copy of it. Assignment means replacing the value of that object.
So far, so simple (except for some undefined situations I can't do anything about).
I don't know what to do about the semantics of any_string. Unlike any_string_ref, any_string creates a copy of the object it is supposed to hold and holds that. This is for situations like the above, where doing otherwise would lead to a reference to a destroyed temporary object.
Basically, I have conflicting semantic requirements for a few functions.
The constructors allocate a new holder for the parameter and initialize it with a copy of the parameter (a template parameter specifies the string class to use for character literals and defaults to std::string).
_held is a member of basic_any_string, a smart pointer to _holder_base, the non-template base class of the template _holder, which is templated on the held type. const_pointer would be const char* or const wchar_t*, of course.
basic_any_string<Ch, Traits, Allocator, DefString>::basic_any_string(
const_pointer s, const allocator_type &alloc)
: _alloc(alloc), _held(new _holder<DefString>(DefString(s)))
The example is not yet valid, for example I have to use the allocator to actually allocate. But never mind that.
The semantics for operator = are far more difficult. If I follow the Boost.Any semantics, = would replace the held object, not the value of the held object. This is necessary for Boost.Any, because the values might not be compatible. For me, this is not necessary, but it would be consistent with the behaviour of the constructors.
On the other hand, the assign members should, in my opinion, not replace the held value, but instead replaces the sequence that the held value controls. Anything else would be inconsistent with the functions append, insert etc.
That's the problem. Because then assign and operator = would do different things, which again is an inconsistency.
These are my options, as I see it:
1) operator = replaces the held object, and so does assign. This makes = consistent with the constructor and assign, but assign will be inconsistent with append.
2) operator = replaces the held object, assign the controlled sequence. This makes = consistent with the constructor and assign with append, but = and assign are inconsistent.
3) operator = replaces the controlled sequence, and so does assign. This makes operator = consistent with assign and assign with append, but = will be inconsistent with the constructor.
For option 3, I can provide a member hold() which replaces the held object.
A similar problem hits me with a swap() function for two any_strings. Should they swap controlled sequences or held objects? The first would retain the storage semantics behind the any_string, the second would be faster. I could also supply a swap_held() method that swaps held objects, while swap() swaps controlled sequences.
I'll be very happy for any good suggestions as to where I should be heading. I believe that such a library could be a great benefit to any programmer, because many people want to control the storage semantics of their strings, something std::string doesn't allow. The result is often an inefficient class hierarchy solution, when C++ actually allows better solutions.
That is, with a good enough compiler. Right now I have a templated class with an inner templated class which contains templated members - I wonder if I'll ever get it to compile...
Thanks in advance.