Well, allowing inheritance from a reference would give the following advantages:
First, we can get the same advantages of extend cast, because the compiler could see that they have the same address, and thus the compiler doesn't need to create a separate variable.
Extend cast has a couple of issues. First, if the compiler ONLY checks the binary layout, then you could convert a vector to an extended string by accident.
If the compiler instead checks that the base class is the same, we basically have a cast that allows us to cast vector A to B, only the memory of A is destroyed?
Or is it? What actually happens to the vector object? Is it now a vector? or is it something else? What is the type of that object now?
What happens if you do:
l1:
vector<foo> a = ...;
l2:
extended_vector<foo> & b = extend_cast<extended_vector<foo>&> (a);
b.bar();
goto l1;
So what happens after the goto statement? Do we call the destructor of b and not the destructor of a? What happens if we do "goto l2;" instead? How do we "unextend" the class?
Reinterpreting the data of the class as though it were another type is done with reinterpret_cast, I'm not sure what the extend cast is supposed to other than make that more defined behavior. But IIRC, reinterpret cast has defined behavior for certain POD types.
What it seems to me that you want is a type safe version of reinterpret cast. But that raises some issues, we can't check "binary compatibility", only whether or not either the POD layout is the same, or the types share some base in some way.
Lets say we only check the binary layout, so you could cast a std::string to a extended_vector<char> because both look something like:
struct { size_t; size_t; char *; };
But they are not compatible. (trailing 0s, SSO could be different, etc.) So instead of casting based on binary layout, lets say you cast based upon the base class.
Now what do we do when we try to "extend_cast" something that wasn't meant to be extended? Like for example having your iterator inherit from your const_iterator. Thus, you could extend_cast a const_iterator to an iterator. Doesn't sound legit to me. That's why I suggest a different syntax. I'm going to improve on it a bit here.
class foo { ... };
class bar : public &foo
{
public:
bar(...) { }
};
Now the only rule is that bar must initialize the reference to foo. This provides a transparent forwarding of the public methods of foo to a user of the bar class, and also provides any additional methods of bar.
It's also possible that ~bar could "convert back" a temporary change.
While this is mostly possible using proxy objects, you have to manually forward every member function. Inheriting from a reference solves the problem in a neater way.
It would also have other uses, consider these theoretical example wrappers around a C string:
class local_c_string
: public &c_string
{
mutable size_t len;
mutable bool len_cached;
public:
local_c_string(c_string & i) : c_string(i), len(0), len_cached(0) {}
bool size() const
{
if( len_cached) return len;
len = strlen();
return len;
}
local_c_string & operator = (c_string & other)
{
static_cast<c_string&>(*this) = other;
len_cached = false;
}
};
The compiler would be able to detect when the references are equal and thus optimize out the declaration, and it doesn't get into any of the wackyness of extend cast.
To avoid the pitfall of thinking that the derived class actually inherits from the reference class, maybe the syntax should be different?
typename &local_c_string localstr(str);
This would help distinguish between objects that are references and objects that contain references. The typename is required because I think the grammar might be ambiguous in templates otherwise.
But I'm not sure if that's an optimal solution, since it would cause reference objects to be treated differently from objects that contain references. To make sure it evaluates correctly using templates, we could say that the full type of a reference object is "&foo" rather than the class name "foo". So then:
class bar
: public &baz
{
...
};
template<typename T>
class foo
{
...
public:
using value_type = T;
};
foo <typename& bar> a;
// Here, a::value_type would be "&bar" rather than "bar" (not to be confused with bar&)
bar b;
// This line would cause a compilation error. bar is not a typename.
&bar b;
// Also an error because it's not clear whether & is the unary operator & or a typename
typename & bar b (a);
// Not an error, typename& used.
This prevents them from being accidentally used where a normal object is expected, but retains the ability to use them in templates without requiring special syntax.
If there is a case where you want the default operator = etc. to function like it would for an object containing a reference rather than an reference object , you could apply the static/inline keyword:
class foo
: static &bar
{
...
};
// Functions like an object containing a reference and the type name is "foo"
class baz
: inline &bar
{
...
};
// Functions like a reference instead of an object. Type name is "&baz"
I'm not sure whether the default should be static or inline though. Maybe always require it explicitly?