You're talking about two different kinds of code, 1) source code size (which affects build time only) and 2) executable size ("compiled code" size, or the code segment). They are correlated, but definitely not the same.
The first is a hard problem to solve in C++, because the language requires monolithic, standalone TUs. (Compare to a language like Go, where the designers learned to avoid this issue from their C experience.) Templates help, as do only using forward declarations ("declarations that are not definitions") when you don't need definitions. (Though it's ironic that templates help, as they require all of their code in headers, in practice.) Mostly long build times are something we just deal with in current C++.
The second can be mitigated with either a common initialization method each ctor can call or a common base. The latter has other advantages, such as when the initialization of members can or must be done in the initialization list. Example:
struct SpanBase {
SpanBase(int start, int stop, int step)
: start(start), stop(stop), step(step)
// any complex code in the init list would normally be duplicated
// in each Span ctor
{
IMAGINE("complex code executed by each Span ctor");
if (start > stop) throw std::logic_error("Span: start exceeds stop");
}
protected:
int start, stop, step;
};
struct Span : protected SpanBase {
// Protected inheritance lets any class derived from Span access members
// which would be protected in Span if SpanBase didn't exist. If Span
// does not have any, then private inheritance can be used.
Span(int stop) : SpanBase(0, stop, 1) {}
Span(int start, int stop) : SpanBase(start, stop, 1) {}
Span(int start, int stop, int step): StepBase(start, stop, step) {}
// this one could be handled by a default value, but that's not always true
};
And finally, C++0x allows you to delegate from one ctor to another, so this whole pattern is vastly simplified.