Why can't I inherit from int in C++?
Asked Answered
R

20

85

I'd love to be able to do this:

class myInt : public int
{

};

Why can't I?

Why would I want to? Stronger typing. For example, I could define two classes intA and intB, which let me do intA + intA or intB + intB, but not intA + intB.

"Ints aren't classes." So what?

"Ints don't have any member data." Yes they do, they have 32 bits, or whatever.

"Ints don't have any member functions." Well, they have a whole bunch of operators like + and -.

Rosinarosinante answered 26/1, 2010 at 22:7 Comment(13)
Languages like C# allow you to add "extension methods" to classes (and just about everything, including ints, are classes), typically used to format into strings or what have you. Of course, this doesn't jive with the nearly-bare-metal C++ model.Risarise
@OP "Ints aren't classes" so? Inheritance, polymorphism and encapsulation are keystones of object oriented design. None of these things apply to ordinal types. You can't inherit from an int because it's just a bunch of bytes and has no code. It doesn't have a method table, so there's no way to add methods or override them.Risarise
As for "Why would I want to? Stronger typing. For example, I could define two classes intA and intB, which let me do intA+intA or intB+intB, but not intA+intB", that's nonsense. You can implement those types without inheriting from int. Just define a new type from scratch, and define its operator+. I don't see the problemCircosta
If your syntax was legal, then intA+intB would compile, which was what you wanted to avoid.Circosta
@jalf: What if you want all the operations integers support? Surely it isn't much joy to implement them for intA, then the same for intB, etc. etc.Winifield
But UncleBens, there's the flaw. If you want type-safe operators that don't work on all int types but only some, you're going to have to re-implement all the operators anyway. You don't save anything by descending from something that already has the operators. And if you do want the operators to work on all int types, then you should just use int.Donner
@UncleBens: It may not be much joy, but it's necessary, because the entire premise for the question was I don't want this type to work in a context where int is expected. It can't reuse any int operators because doing so would enable the exact thing that the OP wanted to avoid.Circosta
This still doesn't mean that one should accept it, and not wonder if there might be different, yet generic and reusable solutions.Winifield
Wouldn't inheriting from int for that purpose make degenerate classes that violate LSP? What's wrong with creating a new class with ints and operators as members?Gunk
@UncleBens: Accept what? The OP proposes a feature that, even if it was possible, would not achieve what he actually wanted to do. Before you can blame the language for not supporting his feature, it's worth considering whether the feature would actually solve his problem. In this case, it wouldn't.Circosta
No, @David, he doesn't. It took him a while to finally figure out what question he really wanted to ask, but in his second comment to your answer, he finally stated it. (I see that a lot in "why" questions, where a person has trouble expressing that he wants to know the rationale for a design decision rather than the chapter and verse that says something's is or isn't allowed.) I see no evidence that he was rejecting answers to his not-yet-finalized question just to annoy people.Donner
You should change the title of your question as what you are looking for is not inheriting from int, but having a different type.Paramorph
Why would one one to make such inheritance? typedef int CostumerId; typedef int ProductId; Costumer getCostumer(CostumerId cid); ProductId pid = 10; Costumer c = getCostumer(pid); // mistake allowed!! if it was class CostumerId: int {}; and class ProductId: int {}; it wouldn't be allowed. If I just compose CostumerId { int id; } I'll have to overload all operators to get the full functionality, like (pid++ == ++pid || pid != pid--).Slob
P
93

Neil's comment is pretty accurate. Bjarne mentioned considering and rejecting this exact possibility1:

The initializer syntax used to be illegal for built-in types. To allow it, I introduced the notion that built-in types have constructors and destructors. For example:

int a(1);    // pre-2.1 error, now initializes a to 1

I considered extending this notion to allow derivation from built-in classes and explicit declaration of built-in operators for built-in types. However, I restrained myself.

Allowing derivation from an int doesn't actually give a C++ programmer anything significantly new compared to having an int member. This is primarily because int doesn't have any virtual functions for the derived class to override. More seriously though, the C conversion rules are so chaotic that pretending that int, short, etc., are well-behaved ordinary classes is not going to work. They are either C compatible, or they obey the relatively well-behaved C++ rules for classes, but not both.

As far as the comment the performance justifies not making int a class, it's (at least mostly) false. In Smalltalk all types are classes -- but nearly all implementations of Smalltalk have optimizations so the implementation can be essentially identical to how you'd make a non-class type work. For example, the smallInteger class is represents a 15-bit integer, and the '+' message is hard-coded into the virtual machine, so even though you can derive from smallInteger, it still gives performance similar to a built-in type (though Smalltalk is enough different from C++ that direct performance comparisons are difficult and unlikely to mean much).

The one bit that's "wasted" in the Smalltalk implementation of smallInteger (the reason it only represents 15 bits instead of 16) probably wouldn't be needed in C or C++. Smalltalk is a bit like Java -- when you "define an object" you're really just defining a pointer to an object, and you have to dynamically allocate an object for it to point at. What you manipulate, pass to a function as a parameter, etc., is always just the pointer, not the object itself.

That's not how smallInteger is implemented though -- in its case, they put the integer value directly into what would normally be the pointer. To distinguish between a smallInteger and a pointer, they force all objects to be allocated at even byte boundaries, so the LSB is always clear. A smallInteger always has the LSB set.

Most of this is necessary, however, because Smalltalk is dynamically typed -- it has to be able to deduce the type by looking at the value itself, and smallInteger is basically using that LSB as a type-tag. Given that C++ is statically typed, there's never a need to deduce the type from the value, so you probably wouldn't need to "waste" that bit on a type-tag.


1. In The Design and Evolution of C++, §15.11.3.

Preceptive answered 27/1, 2010 at 0:22 Comment(11)
I don't know the internal representation of integers in Smalltalk, but a 15-bit integer is not useful in a language trying to work with or extend C. It's too small to be a short, and I doubt it can be used to make compact bitfields. If the cost of being able to derive from raw integer types is one bit of overhead per integer, it's far too high for a language like C++. It's a valid approach in language design in general, but any language that does that will have compatibility issues with C.Cosenza
@David Thornley:while it's true that 15 bits is on the small side, keep in mind that smallInteger was defined sometime before 1980. If I were implementing Smalltalk today, I'd probably make it 63 bits, adequate for all integer types except long long.Preceptive
Do you know if Smalltalk really requires the extra bit? A 63-bit integer is technically legal for int, but it's still going to have compatibility problems with C.Cosenza
"Allowing derivation from an int doesn't actually give a C++ programmer anything significantly new compared to having an int member." regardless of the fact this is the creator of the language, it is clearly incorrect, even back then: inheriting from int would inherit all operator semantics, while having int as a member doesn't, so you are forced to add lots and lots of boilerplateInterjection
@lurscher: Apparently his definition of "significant" is different from yours.Preceptive
This is enough reason to want it: void f(OneInt); void f(AnotherInt);. You don't even need two! I'd like void f(OneInt) to not accept it if AnotherInt is passed to it. I wouldn't even want it to accept a regular int (explicit!).Phototelegraphy
@AndréCaldas: That seems to me more of argument in favor of a "strong typedef" (an alias for an existing type that's still treated as a new, separate type). If you're familiar with Ada, something equivalent to its Type Foo is new Integer;Preceptive
@JerryCoffin: Yes, I would like a strong typedef. :-)Phototelegraphy
@AndréCaldas: I agree that would be nice. Depending on the situation, you can sometimes get by with enum class Foo : underlying_int_type as a semi-reasonable substitute.Preceptive
@JerryCoffin: I actually want a double. :-)Phototelegraphy
@AndréCaldas: Yeah, that's a much tougher one (and a large part of why I agree a strong typedef would be a really nice addition).Preceptive
R
51

Int is an ordinal type, not a class. Why would you want to?

If you need to add functionality to "int", consider building an aggregate class which has an integer field, and methods that expose whatever additional capabilities that you require.

Update

@OP "Ints aren't classes" so?

Inheritance, polymorphism and encapsulation are keystones of object oriented design. None of these things apply to ordinal types. You can't inherit from an int because it's just a bunch of bytes and has no code.

Ints, chars, and other ordinal types do not have method tables, so there's no way to add methods or override them, which is really the heart of inheritance.

Risarise answered 26/1, 2010 at 22:10 Comment(21)
If I want all the methods, it's becomes a massive and error prone class.Rosinarosinante
@Rocketmagnet, I don't think anyone is arguing the merits of a language allowing you to extend the primitive types (many languages do). However, it is just not possible in C++. The best solution for you, in C++, is to build an aggregate class which has an integer member as suggested by this answer.Concertmaster
All what methods? Int doesn't have any.Risarise
All I'd like to know is why. Is there some fundamental reason this could not have been done within the design constraints of C++, or did they forget?Rosinarosinante
@David: Sorry, I meant operators.Rosinarosinante
Why did someone design the Honda Aztek to look so stupid? It was a design decision. That is the only reason.Reremouse
@Rosinarosinante - It comes from the C heritage and what, exactly, primitives represent. A primitive in c++ is just a collection of bytes that have little meaning except to the compiler. A class, on the other hand, has a function table, and once you start going down the inheritance and virtual inheritance path, then you have a vtable. None of that is present in a primitive, and by making it present you would a) break a lot of c code that assumes an int is 8 bytes and b) make programs take up a lot more memory.Protagonist
Mason, making it legal to declare subtypes of int doesn't require adding any storage space to int. It particularly doesn't require adding virtual functions to int. If I have an int subclass that has added virtual functions, and I assign it to an ordinary int variable, then I have a slicing problem, but that's not unique to this situation.Donner
@Rob If I inherit from int it had better have a virtual destructor.Askwith
@Rob, to work properly with virtual functions int would have to have a vtable. if int didn't have any virtual functions, then there would be no benefit from inheriting from it.Schwann
@Reremouse this is quite OT, but the Aztec is by Pontiac, not Honda.Nolin
OK, @Soapbox, I'll pose the same question to you as I did to Neil in the comments to Mason's answer. If there's no benefit to inheriting from something without virtual functions, then why was C++ designed to allow that at all? C++ allows allows descending from class types that have no virtual functions, so int's lack of virtual functions cannot be the reason that C++ forbids descending from it.Donner
@Reremouse The Aztek is a Pontiac. If it was a Honda it would have been very boring and sold a LOT, all of which would still be on the road getting great gas mileage, yet cost a little more than you would expect. And they wouldn't be nearly as ugly.Risarise
About the Honda; that is a bad analogy. Q: Why does the Honda Aztek have 4 wheels? bad A: Because cars have 4 wheels. good A: Three wheels have been tried, and it's unstable.Rosinarosinante
@Rob: Other classes do have methods (even if they aren't virtual) which you could benefit from including in a derived class, including some possibly protected ones which then, of course, you could only get by inheriting from it. Also there's obvious benefit to passing around pointers to base classes so that other libraries can use the original base's methods (virtual or not). Since int has no methods at all, these arguments don't apply. You can get basically all of the functionality you want with operator int().Schwann
"ordinal" types? Perhaps you mean "fundamental" types. Or perhaps the two are just synonyms. I only point it out because there is the type trait std::is_fundamental, and I suspect that evaluates to true for all of the types you refer to.Lying
It would be very useful to say that "the class Foo is a float with methods"; the Go programming language provides an existence proof. The canonical example is Fahrenheit and Celsius classes, each of which simply are floats, but which cannot be added to or multiplied by each other. However, you could implement the "AsCelsius" function on a Fahrenheit, and vice versa.Hime
"Method" is not a well defined word in C++. Different people have different notion of a "method" is.Idomeneus
@JonathanFeinberg "which cannot be added to or multiplied by each other" so both cannot be "float with methods"!Idomeneus
> "If you need to add functionality to "int", consider building an aggregate class which has an integer field, and methods that expose whatever additional capabilities that you require." < Then try to use that class with std::atomic and see how you lose half the functionality.Ranking
@LaurentiuCristofor That would be the case for "object" version of an int or other primitive. I don't see the need for this kind of functionality, but that's not really germane to the question. Guess you could make a ThreadInt and wrap everything in a lock. :DRisarise
C
25

Why would I want to? Stronger typing. For example, I could define two classes intA and intB, which let me do intA+intA or intB+intB, but not intA+intB.

That makes no sense. You can do all that without inheriting from anything. (And on the other hand, I don't see how you could possibly achieve it using inheritance.) For example,

class SpecialInt {
 ...
};
SpecialInt operator+ (const SpecialInt& lhs, const SpecialInt& rhs) {
  ...
}

Fill in the blanks, and you have a type that solves your problem. You can do SpecialInt + SpecialInt or int + int, but SpecialInt + int won't compile, exactly as you wanted.

On the other hand, if we pretended that inheriting from int was legal, and our SpecialInt derived from int, then SpecialInt + int would compile. Inheriting would cause the exact problem you want to avoid. Not inheriting avoids the problem easily.

"Ints don't have any member functions." Well, they have a whole bunch of operators like + and -.

Those aren't member functions though.

Circosta answered 26/1, 2010 at 22:48 Comment(3)
"Inheriting would cause the exact problem you want to avoid." Only if you were to publicly inherit (as, admittedly, the initial question implied in the code section). Private inheritance would not cause this issue.Shows
@Shows Yes, and private inheritance is a useful idiom when you need to override base class member functions without exposing the base class functionality. What would private inheritance of a concrete type without any virtual functions achieve?Idomeneus
+1 for the fact that + and - and such aren't member functions. It might be worth mentioning that even for full blown classes many overloaded operators need not be member functions.Diophantus
P
16

strong typing of ints (and floats etc) in c++

Scott Meyer (Effective c++ has a very effective and powerful solution to your problem of doing strong typing of base types in c++, and it works like this:

Strong typing is a problem that can be addressed and evaluated at compile time, which means you can use the ordinals (weak typing) for multiple types at run-time in deployed apps, and use a special compile phase to iron out inappropriate combinations of types at compile time.

#ifdef STRONG_TYPE_COMPILE
typedef time Time
typedef distance Distance
typedef velocity Velocity
#else
typedef time float
typedef distance float
typedef velocity float
#endif

You then define your Time, Mass, Distance to be classes with all (and only) the appropriate operators overloaded to the appropriate operations. In pseudo-code:

class Time {
  public: 
  float value;
  Time operator +(Time b) {self.value + b.value;}
  Time operator -(Time b) {self.value - b.value;}
  // don't define Time*Time, Time/Time etc.
  Time operator *(float b) {self.value * b;}
  Time operator /(float b) {self.value / b;}
}

class Distance {
  public:
  float value;
  Distance operator +(Distance b) {self.value + b.value;}
  // also -, but not * or /
  Velocity operator /(Time b) {Velocity( self.value / b.value )}
}

class Velocity {
  public:
  float value;
  // appropriate operators
  Velocity(float a) : value(a) {}
}

Once this is done, your compiler will tell you any places you have violated the rules encoded in the above classes.

I'll let you work out the rest of the details yourself, or buy the book.

Penland answered 27/1, 2010 at 0:43 Comment(6)
Thanks Alex. This is roughly what I'm after. I just wondered if it would be possible to do this without having to re-specify all the operators every time.Rosinarosinante
You reference Effective C++. What part do you mean? "Item 18: Make interfaces easy to use correctly and hard to use incorrectly", with its Date example?Marcionism
Once you've implemented the strong type compile, why even leave weak typing as a compile option?Sundog
Performance, code density, icache usage, debuggability, handling platforms with incomplete/incompatible template implementations.Penland
"Performance, code density," : wouldn't a modern compiler completely inline such classes at high optimisation level ? I tried on gcc.godbolt.org and at -O3 it compiles to the exact same ASM instructions: pastebin.com/sbqvXxP7Sheasheaf
Not if the types are defined in a library, they won'tPenland
P
10

Because int is a native type and not a class

Edit: moving my comments into my answer.

It comes from the C heritage and what, exactly, primitives represent. A primitive in c++ is just a collection of bytes that have little meaning except to the compiler. A class, on the other hand, has a function table, and once you start going down the inheritance and virtual inheritance path, then you have a vtable. None of that is present in a primitive, and by making it present you would a) break a lot of c code that assumes an int is 8 bytes only and b) make programs take up a lot more memory.

Think about it another way. int/float/char don't have any data members or methods. Think of the primitives as quarks - they're the building blocks that you can't subdivide, you use them to make bigger things (apologies if my analogy is a little off, I don't know enough particle physics)

Protagonist answered 26/1, 2010 at 22:8 Comment(21)
That's not an answer, just a restatement of the fact.Rosinarosinante
@Rocketmagnet, that does not preclude it from being an answer. Your original question asked why it could not be done. You may not have know it was because int is not a class; in some languages int is a class.Concertmaster
OK, why is int not a class ? To me it makes sense to inherit from an int. It would be an object which behaves just like an int.Rosinarosinante
If it would behave just like an int, why would you re-invent int? :]Flannelette
It was a design decision when C++ was created that the builtin types would not be classes. Think about it another way. int/float/char don't have any data members or methods. Think of the primitives as quarks - they're the building blocks that you can't subdivide, you use them to make bigger things (apologies if my analogy is a little off, I don't know enough particle physics)Protagonist
@Rocketmagnet, most of us will never know why our 'pet features' of any language are not implemented or not implemented the way we think they should be. C++ was designed by committee and this is what they decided on.Concertmaster
@GMan: I think you meant to say "Why would you re-invent int?"Reremouse
@GMan: So punny. Praise The Dood for comment editing abilities. Also +1 for this answer because it is the correct one.Reremouse
@Mason: It as also a design decision when Java was created, and the Java designers were going heavily into object orientation. They did provide wrapper classes, but somebody could write a wrapper template in C++.Cosenza
I don't see anything about "function tables" being part of a class in the standard, Mason. Classes have member functions, but they don't contribute anything to the size of an object.Donner
@Concertmaster The core of C++, still clearly visible today, was not designed by a committee - it was designed (and implemented) principally by one man. Almost all the features in the current C++ standard were present in Cfront 3.0, which well predates it.Askwith
@Rob Virtual functions must be implemented somehow, and that implementation must add to the size of an instance. And if you don't have virtual functions, why have inheritance?Askwith
@Neil, if inheritance is pointless without virtual functions, then why would C++ be designed to allow inheritance without virtual functions?Donner
@Rob Because it was designed that way. Not every design decision is a good one, but I can't imagine using inheritance without virtual functions - certainly, I've never done so.Askwith
@Mason: So we know you're not a string theorist, then. :)Flannelette
@Neil, good point about Bjarne not being a committee - I guess we know exactly who to blame then.Concertmaster
That's begging the question, @Neil. Every decision has a reason. I can think of a few for why this one might have been made. 1. Stroustrup or the committee knew that inheritance without virtuals was useless, but didn't bother formalizing a restriction in the standard — users would figure it out for themselves eventually anyway. 2. There are reasons for wanting inheritance without virtuals, so it was decided to allow it. 3. It wasn't a decision at all; the issue simply never occurred to anyone at the time. 4. Someone flipped a coin.Donner
@Rob: If you read Design and Evolution, you will find that Stroustrup made design decisions that looked good at the moment that he later regretted (I think protected inheritance is the best example). However, Stroustrup wanted to leave options open to the programmer, and was loath to forbid anything without good reason.Cosenza
That's interesting @David. (I can't actually find protected inheritance mentioned in D&E, I'll believe you if you say it's there.) I'm not sure I understand your point with regard to what I've already written, though.Donner
@Rob: I'm commenting on your reasons: you seem to have left out "It looked like a good idea to Stroustrup at the moment", as well as commenting on #1: Stroustrup didn't like to make things illegal just because he didn't see a good use. The "protected inheritance" thing may be a fault in my memory, but section 13.9 is about protected data members, and that may be what I was remembering badly.Cosenza
A class in C++ does not have a function table, or any other source of overhead. I would expect operations on class Int { public: int x; }; to compile down to the same code as int.Theta
F
5

No one has mentioned that C++ was designed to have (mostly) backwards compatibility with C, so as to ease the upgrade path for C coders hence struct defaulting to all members public etc.

Having int as a base class that you could override would fundamentally complicate that rule no end and make the compiler implementation hellish which if you want existing coders and compiler vendors to support your fledgling language was probably not worth the effort.

Friedman answered 27/1, 2010 at 0:35 Comment(5)
I don’t see that as valid. The original int wouldn’t behave any different to the int form which you could inherit. And it’s not like C had a method to check whether a type is expected to be a class.Sleet
@Sleet BS. If you're using pointer arithmetic to iterate through a malloc'd block of memory, increment by sizeof(int) should be by 2 or 4 bytes ( depending on the bandwidth of your system as defined in limits.h) not by the instance size of an Int, which will certainly be different. Go write a compiler and let us know how it goes.Risarise
Why would the instance size be different? It doesn’t carry any information apart from the value of an int? I don’t need my own compiler to prove that.Sleet
@David: From the point of view of language design, an Int class that has no ability to have virtual functions is very limited as a base class, and therefore arguably not worth having. From the point of view of implementation, a class with virtual functions needs one vtable pointer per instance.Cosenza
@David: There would be no vtable, because there are no virtual functions. The derived int class would be the same size as the original int.Rosinarosinante
W
4

As others I saying, can't be done since int is a primitive type.

I understand the motivation, though, if it is for stronger typing. It has even been proposed for C++0x that a special kind of typedef should be enough for that (but this has been rejected?).

Perhaps something could be achieved, if you provided the base wrapper yourself. E.g something like the following, which hopefully uses curiously recurring templates in a legal manner, and requires only deriving a class and providing a suitable constructor:

template <class Child, class T>
class Wrapper
{
    T n;
public:
    Wrapper(T n = T()): n(n) {}
    T& value() { return n; }
    T value() const { return n; }
    Child operator+= (Wrapper other) { return Child(n += other.n); }
    //... many other operators
};

template <class Child, class T>
Child operator+(Wrapper<Child, T> lhv, Wrapper<Child, T> rhv)
{
    return Wrapper<Child, T>(lhv) += rhv;
}

//Make two different kinds of "int"'s

struct IntA : public Wrapper<IntA, int>
{
    IntA(int n = 0): Wrapper<IntA, int>(n) {}
};

struct IntB : public Wrapper<IntB, int>
{
    IntB(int n = 0): Wrapper<IntB, int>(n) {}
};

#include <iostream>

int main()
{
    IntA a1 = 1, a2 = 2, a3;
    IntB b1 = 1, b2 = 2, b3;
    a3 = a1 + a2;
    b3 = b1 + b2;
    //a1 + b1;  //bingo
    //a1 = b1; //bingo
    a1 += a2;

    std::cout << a1.value() << ' ' << b3.value() << '\n';
}

But if you take the advice that you should just define a new type and overload the operators, you might take a look at Boost.Operators

Winifield answered 26/1, 2010 at 23:22 Comment(0)
T
3

What others have said is true... int is a primitive in C++ (much like C#). However, you can achieve what you wanted by just building a class around int:

class MyInt
{
private:
   int mInt;

public:
   explicit MyInt(int in) { mInt = in; }
   // Getters/setters etc
};

You can then inherit from that all you jolly want.

Targett answered 26/1, 2010 at 22:24 Comment(6)
Ok, but you'll want to mark the constructor as explicit to prevent automatic conversion.Figment
But this class behaves nothing like an int.Rosinarosinante
You have to go to the trouble of adding all the member operators abd typecasting functions to get it to act like an extended int type. But once you do, you can then make it a template type that takes min and max allowable values, and all of the member functions can do range checking.Tetra
Sorry Rocket, you want to "inherit" from int you gotta do the work :)Targett
@Rosinarosinante Not behaving like an int is your point, remember: you want stronger type checking. Allowing any operations like integers do is the opposite of that.Idomeneus
What is the preferred method of testing for this? is_arithmetic, is_scalar, is_fundamental, etc.Robbins
P
3

You can get what you want with strong typedefs. See BOOST_STRONG_TYPEDEF

Paramorph answered 2/5, 2010 at 13:59 Comment(1)
This is perfect if - like me - one wants to type check everything and then is OK with a loose typedef. This is not as elegant as @Alex's answer (from Scott Meyers), but cost me one line of code compared to the loose typedef!Simplism
A
3

This is a very old topic, but still relevant to many.

Unit-aware programming provides one very important reason why inheriting from intrinsic/fundamental types would be valuable in C++. Numerous well-developed solutions now exist to this problem, but all of them require templates in order to achieve what might otherwise have been handled directly with inheritance, polymorphism, and the strong type-checking of C++. The following is one such alternative for unit-aware programming:

https://benjaminjurke.com/content/articles/2015/compile-time-numerical-unit-dimension-checking/

Inheriting from raw pointers also makes sense (and is also illegal). We can easily create a class where we inherit from a smart pointer thereby extending its behavior, but due to the limitation of C++, we are unable to do the same with a raw pointer. This means that our only way to extend behavior of raw pointers like char* is to write functions that require the pointer to be passed as an argument to the function instead of making them look more like methods you might see within std::string.

Of course, we can always use composition instead of inheritance to get the same effect, but in so-doing, we lose all of the intrinsic operations (such as operator[] and operator++ for a char*) and must remap every one of them that we need to support. Is it doable? Sure. I've done it. Is it easy? Not necessarily. That depends on how much you need to map and how quickly it needs to get done. Is it fast? Depends.

With all of that said, in my view, the biggest argument in favor of inheriting behavior from intrinsic types (including raw pointer types) is that it demonstrates the conceptual consistency of the language. Overall, C++ is very consistent, but it breaks down a bit here in my book as intrinsic types are considered to be special, and frankly, they are not.

Agonist answered 15/10, 2021 at 2:33 Comment(0)
C
2

In C++ the built-in types are not classes.

Concertmaster answered 26/1, 2010 at 22:8 Comment(0)
S
2

Well, you don’t really need to inherit anything which hasn’t got any virtual member functions. So even if int were a class, there would not be a plus over composition.

So to say, virtual inheritance is the only real reason you’d need inheritance for anyway; everything else is just saving you masses of typing time. And I don’t think an int class/type with virtual members would be the smartest thing to imagine in the C++ world. At least not for you every day int.

Sleet answered 27/1, 2010 at 0:36 Comment(0)
A
1

What does it mean to inherit from an int?

"int" has no member functions; it has no member data, it's a 32 (or 64) bit representation in memory. It doesn't have it's own vtable. All what it "has" (it doesn't really even own them) are some operators like +-/* that are really more global functions than member functions.

Askwith answered 26/1, 2010 at 22:13 Comment(16)
int has a bunch of operators. int has member data consisting of 32 or 64 bits.Rosinarosinante
@Rocketmagnet: The bits of an int are not members of an int. Twisting words does not alter reality.Dagall
What difference does that make, @Chuck? An int holds data. A descendant of an int could hold that data just as easily, no? Why does it need to be called member data?Donner
@Rob Kennedy: What difference does it make? It makes all the difference. Sure, a "descendent" of an int could be represented by the same bit pattern — in fact, that's all it could do, because an int does not have members. A descendent that can't be any different from its parent is no different from a typedef.Dagall
Just because the base type doesn't "have members" doesn't mean a descendant couldn't add members.Donner
Oh, and Int doesn't "have" operators. The fundamental operators that are part of the language grammar support int, and char, and other ordinal types, but these are part of the language, not methods of a class. At some point, operators have to be resolved to atomic operations. Otherwise, the whole language would eat its own tail and the world would implode into a silly paradox.Risarise
@GMan, Chuck, David: This is all true and right, but it doesn't answer the question "why is it that way". Those were deliberate design decisions. Stating the outcome of these decisions doesn't explain why they were made.Sidsida
@sbi: From my point of view, there's far too many questions about "why is it that way" that seem to assume that a language design emerges full-blown and logically consistent from somewhere, or alternately assume that the language was designed to annoy them by leaving off some favorite feature. There are simply no good answers for many of these questions, aside from "It looked like a good idea to Ritchie/Stroustrup/Gosling/whoever at the time" or "We needed to maintain some compatibility with X".Cosenza
@David: I don't see Rocketmagnet rejecting answers because they aren't logical. What I see mostly is people restating the facts ("because they're built-ins!") as an attempt to answer why the facts are the way they are. If you think ints aren't inheritable because Stroustrup thought nobody wanted the feature or because he considered it blatantly stupid to even think of it or whatever - go and answer the question that way. But it is a perfectly legal question that ought to get better answers than the "because!" it mostly got in this thread.Sidsida
@sbi: And my point is that these questions often have no good answer. A lot of language design decisions are made for no particular reason, and aren't changed later because of compatibility. It happens that Jerry Coffin dug up Stroustrup's reasoning on this case, which is good. Sometimes "because" is the best answer, and it annoys me when people refuse to accept it.Cosenza
@David: No, that "because" never is a good answer. It's just an attempt to avoid saying "I don't know" or simply shutting up. What if everybody who didn't know the Why refrained from answering "because"? We'd have a perfectly valid question with the usual three or four answers, at least one of which hit the nail right on the head. Instead we have all this noise where everybody tried to hide the fact that they don't know and earn reputation nevertheless.Sidsida
@sbi: Sometimes "because" is the only answer. Why does Planck's constant have the value it does? Sometimes the only answer is that it seemed like a good idea at the time. If nobody provided an answer that wasn't certainly good, some questions about language features would go unanswered. Which, I must admit, might be an improvement.Cosenza
There's quite a difference: Planck's constant was discovered, while C++' inheritance mechanism was deliberately designed.Sidsida
@Sidsida "it's like that"/"just because"/"it's the Law" is a valid answer when you know that there is not a better answer, but SO moderators don't like this answer and will remove it without a rational (and no, "not an answer" is not a rational)Idomeneus
@DavidLively "Otherwise, the whole language would eat its own tail and the world would implode into a silly paradox" No it would not. malloc is not defined in term of mmap. You cannot write mutex in term of std C++. Integers could be part of the std lib.Idomeneus
@DavidLively: However, there is a better answer.Sidsida
G
1

This answer is an implementation of UncleBens answer

put in Primitive.hpp

#pragma once

template<typename T, typename Child>
class Primitive {
protected:
    T value;

public:

    // we must type cast to child to so
    // a += 3 += 5 ... and etc.. work the same way
    // as on primitives
    Child &childRef(){
        return *((Child*)this);
    }

    // you can overload to give a default value if you want
    Primitive(){}
    explicit Primitive(T v):value(v){}

    T get(){
        return value;
    }

    #define OP(op) Child &operator op(Child const &v){\
        value op v.value; \
        return childRef(); \
    }

    // all with equals
    OP(+=)
    OP(-=)
    OP(*=)
    OP(/=)
    OP(<<=)
    OP(>>=)
    OP(|=)
    OP(^=)
    OP(&=)
    OP(%=)

    #undef OP

    #define OP(p) Child operator p(Child const &v){\
        Child other = childRef();\
        other p ## = v;\
        return other;\
    }

    OP(+)
    OP(-)
    OP(*)
    OP(/)
    OP(<<)
    OP(>>)
    OP(|)
    OP(^)
    OP(&)
    OP(%)

    #undef OP


    #define OP(p) bool operator p(Child const &v){\
        return value p v.value;\
    }

    OP(&&)
    OP(||)
    OP(<)
    OP(<=)
    OP(>)
    OP(>=)
    OP(==)
    OP(!=)

    #undef OP

    Child operator +(){return Child(value);}
    Child operator -(){return Child(-value);}
    Child &operator ++(){++value; return childRef();}
    Child operator ++(int){
        Child ret(value);
        ++value;
        return childRef();
    }
    Child operator --(int){
        Child ret(value);
        --value;
        return childRef();
    }

    bool operator!(){return !value;}
    Child operator~(){return Child(~value);}

};

Example:

#include "Primitive.hpp"
#include <iostream>

using namespace std;
class Integer : public Primitive<int, Integer> {
public:
    Integer(){}
    Integer(int a):Primitive<int, Integer>(a) {}

};
int main(){
    Integer a(3);
    Integer b(8);

    a += b;
    cout << a.get() << "\n";
    Integer c;

    c = a + b;
    cout << c.get() << "\n";

    cout << (a > b) << "\n";
    cout << (!b) << " " << (!!b) << "\n";

}
Genera answered 2/10, 2014 at 3:7 Comment(6)
I wrote my answer before i saw your (we have difference 1.5 hours between our answers). But i'm not sure that your code looks complicated. Could you comment any issue from more simpler code: https://mcmap.net/q/239290/-why-can-39-t-i-inherit-from-int-in-cAdagio
@Speakus I don't see your post the link you provided doesn't seem to go to a particular post. I've been coding in c++ so much everything here looks very simple to me. I'll be glad to explain my answer further, just let me know what you don't understand.Genera
my answer was deleted: so link i want to show here: github.com/Speakus/cppFundamentalClass/blob/master/…Adagio
In your code you also implement for TT (another type other than T). This removes the strong typeness which is sought after. Also you return a Primitive<T> rather than the derived type.Thus again not being useful to the purpose of strong typeness. Try running UncleBens main() function using your class. That should shed light where you need to improve.Genera
i really miss strong typing request. Now it's supported in my code too: github.com/Speakus/cppFundamentalClass/commit/… - tested with UncleBens main() function :)Adagio
Getting better :). using an enum (or number) is not a good idea because someone else can define a Primitive<T,N> of the same number without knowing one already exists. You can do template<class T, class B> class Primitive : public B {... and pass empty named structs to for BGenera
A
1

Please, excuse me for my poor English.

There is a major difference between C++ correct construction like this:

struct Length { double l; operator =!?:%+-*/...(); };
struct Mass { double l; operator =!?:%+-*/...(); };

and the proposed extension

struct Length : public double ;
struct Mass   : public double ;

And this difference lies on the keyword this behavior. this is a pointer and using a pointer let few chances to use registers for computations, because in usuals processors registers does not have address. Worst, using pointer make the compiler suspicous about the fact that two pointers may designate the same memory.

This will put an extraordinary burden on the compiler to optimize trivials ops.

Another problem is on the number of bugs: reproducing exactly all the behavior of operators is absolutly error prone (for ex making constructor explicit does not forbid all implicits cases). Probability of error while building such an object is quite high. It is not equivalent to have the possibility to do something thru hard work or to have it already done.

Compiler implementors would introduce type checking code (with maybe some errors, but compiler exactness is much better than client code, because of any bug in compiler generate countless errors cases), but main behavior of operation will remain exactly the same, with as few errors than usual.

The proposed alternate solution (using structs during debug phase and real floats when optimized ones) is interesting but has drawbacks: it's raise the probability to have bugs only in optimized version. And debugging optimized application is much costly.

One may implement a good proposal for @Rocketmagnet initial demand for integers types using :

enum class MyIntA : long {}; 
auto operator=!?:%+-*/...(MyIntA);
MyIntA operator "" _A(long);

The bug level will be quite high, like using single member trick, but compiler will treat thoses types exactly like built-in integers (including register capability & optimisation), thanks for inlining.

But this trick can't be used (sadly) for floating numbers, and the nicest need is obviously real valued dimensions checking. One may not mix up apples and pears: adding length and area is a common error.

Stroustrup' invocation by @Jerry is irrelevant. Virtuality is meaningful mainly for public inheritance, and the need is here toward private inheritance. Consideration around 'chaotic' C conversion rules (Does C++14 have anything not chaotic?) of basic type are also not useful : the objective is to have no default conversion rules, not to follow standard ones.

Archibald answered 18/8, 2015 at 14:24 Comment(1)
I realize your answer is getting on in age, but the idea that this needs to be a pointer is a non-issue. The type and the derived class both have storage allocated to them. Yes, their current values pass through registers quite regularly, but ultimately a variable of type, e.g., int, lives in certain bytes of memory, which have an address that can be pointed to by this. Thus, *this would be of type int and this would point to where the int was stored. I personally believe the only reason this isn't possible is purely because a lot of compiler writers don't want to tackle the work.Daybook
U
1

AFAICT, all the other answers miss the point.

Yes, of course, it's not possible because it's the standard says so...

Part of the problem is the way your question is structured, you wouldn't get what you are claiming to want if int was inheritable in the way that seems to be implied by your question.

However, I think what you really are wishing-for/asking-why-it-doesn't-exist is a CRTP/tagged int template:

class MyInt : public int<MyInt>
{
};

Such a thing would simply be a short hand for a bunch of operators on your custom type.

That probably doesn't exist because:

  • there's probably a lot of subtle gotchas that would make it less desirable
    • conversion to/from other types may or may not desirable
  • it was possible to get the same effect another way (though with much more work)
  • might have been a pain to implement in the compilers (lots of things used to not get implemented because of difficulty)
  • templates came a bit later.
  • because no one thought of it at the time.
Unsteel answered 29/11, 2023 at 16:40 Comment(0)
D
0

More general than the fact that "int is primitive" is this: int is a scalar type, while classes are aggregate types. A scalar is an atomic value, while an aggregate is something with members. Inheritance (at least as it exists in C++) only makes sense for an aggregate type, because you can't add members or methods to scalars — by definition, they don't have any members.

Dagall answered 26/1, 2010 at 23:40 Comment(5)
for some, class is syntax to express a type, and subclassing means restricting the domain. like naturals are a subtype of integers, which are a subtype of reals, etc.Ushaushant
Guess what? Those "some" are living in la la land. You're talking about how you wish it were, not how it is. I deal in the real world. class is not a generic syntax to express the mathematical concept of type in C++.Dagall
What you say is all well and good, except that the question wasn't "how is it?" but "why is it that way?" Neither your answer nor your (somewhat rude) comment answers the right question.Sidsida
The question was "Why can't I do this?" The answer is "Because int is a scalar, and the C++ concept of subtyping is not meaningful for scalars." Integers weren't arbitrarily excluded — it's not possible to apply the concept as it exists to them. That is why.Dagall
No that isn't why. That's just stating the facts without explaining why they are the way they are. There is a very good answer to these questions and, not surprisingly, it also is the accepted answer.Sidsida
H
-1

If I remember, this was the main - or one of - the main reasons C++ was not considered a true object oriented language. Java people would say: "In Java, EVERYTHING is an object" ;)

Honeymoon answered 26/1, 2010 at 22:27 Comment(4)
Except that Java also has an int that can't be subclassed, so those people would be wrong :-)Forsook
@Joe: Thankyou, someone who understands the question.Rosinarosinante
Note that people have different definitions of object orientation, so it would be more accurate to say "some people considered C++ not a true O-O language" or specify who.Cosenza
In C++ an int is an object. It isn't a class. And not all classes have virtual functions, not all classes are designed to serve as base classes.Idomeneus
T
-3

This is related to how the items are stored in memory. An int in C++ is an integral type, as mentioned elsewhere, and is just 32 or 64 bits (a word) in memory. An object, however, is stored differently in memory. It is usually stored on the heap, and it has functionality related to polymorphism.

I don't know how to explain it any better. How would you inherit from the number 4?

Tem answered 26/1, 2010 at 22:56 Comment(9)
Objects are usually stored on the heap in C++? Isn't that a bit of an overgeneralization?Dagall
Also, number 4 isn't a type. It's a value that a variable of type int might hold. Also objects don't necessarily have to be polymorphic (see your STL).Winifield
Here are two variables: int x; struct { int i; } y; Erik, please describe how x and y are stored differently in memory.Donner
@UncleBens: There are languages with prototype-based object system, where you can inherit from an instance. This is pretty standard in languages for writing text adventures/interactive fiction, such as Inform and TADS. It's just that C++ doesn't work that way.Cosenza
I don't want to inherit from the number 4, I want to inherit from int.Rosinarosinante
@Rob: Structs are also integral (basic) types. They cannot be instantiated, and are not objects.Tem
@Chuck: Where objects are stored, was not the main point of my post. And, you CAN store objects on the heap, if I recall correctly (it's been quite a while since I did C++ programming), But, usually you have a reference to your object (on the stack), and the object itself is stored on the heap. The pointer is destroyed when you exit your scope, however, the object lives on if you don't delete it. This could lead to memory leaks.However, my point was, integral types are not stored in the same way as objects, and it doesn't make sense to inherit from them. Then they would have to be object typesTem
@Eric, you seem to have no idea what you're talking about. In my previous comment, y is an instance of the anonymous struct defined right there. If it had a name (say, Y), you could also instantiate it like this: Y* z = new Y; The variables x, y, and now z, are all objects. (Check the standard for its definition of object; if you think it's "instance of a class," you're wrong.) You can store objects on the heap, but saying they're usually there is an overstatement. They're frequently on the stack, too. Integral types are stored the same as other types.Donner
@Rob: I won't get into an argument of C++ details, I'm not proficient enough in the language (far from) to do so. However, I think we are getting a bit off track. The initial question was why the author couldn't inherit from an int (which is a basic type). I'll stop arguing this now, as I feel we're getting a bit OT :)Tem
P
-4

Why can't you inherit from int, even though you might want to?

Performance

There's no functional reason why you shouldn't be able (in an arbitrary language) inherit from ordinal types such as int, or char, or char* etc. Some languages such as Java and Objective-C actually provide class/object (boxed) versions of the base type, in order to satisfy this need (as well as to deal with some other unpleasant consequences of ordinal types not being objects):

language     ordinal type boxed type, 
c++          int          ?
java         int          Integer
objective-c  int          NSNumber

But even Java and objective-c preserve their ordinal types for use... why?

The simple reasons are performance and memory consumption. An ordinal type can be typically be constructed, operated upon, and passed by value in just one or two X86 instructions, and only consumes a few bytes at worst. A class typically cannot - it often uses 2x or more as much memory, and manipulating its value may take many hundreds of cycles.

This means programmers who understand this will typically use the ordinal types to implement performance or memory usage sensitive code, and will demand that language developers support the base types.

It should be noted that quite a few languages do not have ordinal types, in particular the dynamic languages such as perl, which relies almost entirely on a variadic type, which is something else altogether, and shares some of the overhead of classes.

Penland answered 27/1, 2010 at 0:1 Comment(4)
You can easily construct wrapper classes in C++ that adds semantics to primitive types but compiles down to the exact same assembly as using the primitive directly, (or calling a non-member function that operates on the primitive). A language need not go the Java way and provide you an Integer class that behaves differently than the primitive equivialent.Decretal
yes, you are right, you can. But they aren't provided out of the box, hence the ? in the table.Penland
also, if there were provided out of the box, they would have virtual functions and be based upon a base class and have members in addition to their main value. count on it. For example, the Java Int type has a lock in it, as well as the int (and who knows what else?)Penland
Alex, I disagree. int has no virtual functions, therefore there will be no vtable. If I add no other members, then MyInt is exactly the same size as int, and should compile to identical code. The only difference will be compile time checking of my types.Rosinarosinante

© 2022 - 2024 — McMap. All rights reserved.