Wondering why you didn't have anything to say here. Assuming it's
because you agreed...
No, but it's usually worthwhile to ponder how a particular wrinkle of
the language pertains to and is (or is not) consistent with the overall
philosophy of the language. Consider it from the following
perspective. A typedef exists in a similar role to a
forward-declaration; it is a mechanism for talking about the existence
of a type. Both might be considered "first order" type-related
entities, since they deal with types directly. A forward-declaration
*of* a typedef is more indirect by one degree, and introduces a new
category, "second order" type-related entities. Such indirection can
increase indefinitely, so a language designer must decide on a
reasonable compromise between generality and practicality. In C++, the
line is drawn at the first order.
Incidentally, none of the above language is in any way
"official"/"canonical" jargon -- it's just what I made up on the spot
to express my point.
Although I'm clear about the "order" distinction you're drawing
here, I have to admit I don't understand the utility. For a compiler
writer, "class Foo;", "typedef Foo Bar;", and "template<class
T> Fee;" each introduce a single new identifier into the type
namespace. The compiler doesn't care how many names have been
introduced before. Right? So, there's no real distinction between
the 1st order, and the nth order.
I wouldn't say that. I don't see in what way class template
instantiations are different from regular classes in this context, so
I'm a bit baffled as to why you've singled them out. And introducing a
new "wrapper" type for every template instantiation would just defeat
the whole purpose of templates.
The reason they are different is because you cannot forward declare a
type identifier that's based on a templated type without introducing
the name and signature of the underlying template. That's badness
and reduces the ability of the interface designer to refactor or modify
the underlying implementation in the future.
Do you see how they are different now?
To be clear, I'm not arguing to wrap every template declaration
(which does defeat the purpose). And I'm not arguing to wrap every
possible template instantiation (which would be impossible).
I am arguing any "class like" type identifiers that are exported
from a public interface should be wrapped (if it's not a class
already). This would definitely include any identifiers that export
specific template specializations (e.g. std::string, or IntVec3d). If
these specializations are what you mean by "instantiation", then
yes, every exported instantiation should be wrapped.
Concretely, the STL string identifier should be declared "struct
string : public basic_string<char> {};" instead of "typedef
basic_string<char> string;" so it can be reasonably forward declared.
Right?
No, because then you lose the ability to interoperate std::string with
std::basic_string<char> (incidentally, there are in actuality three
template parameters)[1], without introducing additional conversion logic.
Useful sometimes perhaps, but not a universal solution by any means.
As I'm sure you're aware, the empty curly braces are not nearly
empty in actual practice. Since the new class masks any constructors
in the base class with its default constructor, all of the constructors
in the base class will need to be hoisted up a level. (There a number
of them in basic_string<char>.)
Not true (to your lack of interoperability assertion). As I mention in
the subsequent paragraph, your solution does need to include
declarations for any constructors in the underlying class.
Incidentally, it will also need declarations of any explicit assignment
operators in the underlying class (if there are any) since the default
assignment operator will mask those as well.
If you do the required work, then "typedef basic_string<char>
string;" declaration will be indistinguishable[2] from the "struct
string : basic_char<char> {...};" form except you can then forward
declare with "class string;".
Here's some working code. Try it.
namespace std
{
class better_string : public basic_string<char>
{
public:
better_string(const allocator_type& a = allocator_type()) :
basic_string<value_type>(a) {}
better_string(const basic_string<value_type>& str,
size_type pos = 0, size_type n = npos, const allocator_type& a =
allocator_type()) :
basic_string<value_type>(str, pos, n, a) {}
better_string(const value_type* s, size_type n, const
allocator_type& a = allocator_type()) :
basic_string<value_type>(s, n, a) {}
better_string(const value_type* s, const allocator_type& a
= allocator_type()) :
basic_string<value_type>(s, a) {}
better_string(size_type n, value_type c, const
allocator_type& a = allocator_type()) :
basic_string<value_type>(n, c, a) {}
better_string(const_iterator begin, const_iterator end,
const allocator_type& a = allocator_type()) :
basic_string<value_type>(begin, end, a) {}
better_string& operator=(const basic_string<value_type>&
str)
{ basic_string<value_type>:

perator=(str); return *this;
}
better_string& operator=(const value_type* str)
{ basic_string<value_type>:

perator=(str); return *this;
}
better_string& operator=(value_type c)
{ basic_string<value_type>:

perator=(c); return *this; }
};
}
Like I said, the curly braces are not quite empty (and won't be for
most non-trivial classes).
This covers all of the documented call signatures according to
Addison-Wesley's "STL Tutorial and Reference Guide, Second
Edition." I don't have a copy of the ANSI/ISO STL document, but
the construct is easy to extend/modify if I've missed anything.
Here's some code that exercises better_string a little bit. Note
it's completely interoperable with std::string as well as classic C
strings (char*).
void better_test()
{
string s1("some text");
better_string bs1(s1);
assert(bs1 == s1);
assert(bs1 == "some text");
bs1 = 'X';
assert(bs1 == "X");
bs1 = "test";
assert(bs1 == "test");
bs1 = s1;
assert(bs1 == "some text");
}
Another very good reason not to use inheritance willy-nilly. It's good
when it's good, and in all other cases it's not good.
Luke, this is hardly willy-nilly. This is to decrease the coupling
between a library and its clients which is always good.
I'm having a hard time imagining a public interface where it would be
good to export an identifier with the "typedef Bar<Foo> FooBar;"
syntax. To be clear, "good" means better in any sense than
exporting the same identifier with your "struct FooBar: Bar<Foo>
{...};" syntax.
[1] Yes, I'm aware there are three parameters in the basic_string
template definition. As you're aware, the second and third
parameters have defaults, so basic_string<char> is *identical* to
basic_string<char, char_traits<char>, allocator<char> > just a lot
shorter (and a lot clearer about the intent, IMHO).
[2] What I mean by "indistinguishable" is that any code that
compiled when std::string was defined as "typedef basic_string<char>
string;" will compile with the new declaration. Except of course the
gross forward declaration currently required, but that's exactly what
I'm hoping to get rid of. More importantly, the compiled code will
behave the same as the code compiled under the inferior declaration.