6

I'm trying to build a template that will let me use a resizable array. Is there a way to find the sizeof(T)? I'm using malloc rather than new because I want to use realloc in the function that resizes the array. This is the constructor for my class that is getting errors:

template <class T>
set<T>::set(void) {
arr = malloc(10 * sizeof(T));
numElts = 0;
size = 10;
};

I get the following error message when trying to build:

error C2440: '=' : cannot convert from 'void *' to 'int *'
1>          Conversion from 'void*' to pointer to non-'void' requires an explicit cast
1>          c:\set.cpp(42) : while compiling class template member function 'set<T>::set(void)'
1>          with
1>          [
1>              T=int
1>          ]

In the main function I'm calling it with:

set<int> *set1 = new set<int>();

From the research I've done, it looks like the compiler has no way of knowing what to use for sizeof(T), so it can't compile. How else would I go about this?

Dom12
  • 61
  • 1
  • 1
  • 2
  • 4
    Keep in mind that your `set` would only be able to work with POD types. – K-ballo Nov 03 '11 at 22:22
  • 5
    The error *clearly* states "cannot convert from `void *` to `int *`", and even goes on to suggest that you need an explicit cast to pull that off. Clearly, the problem is that it does not know what `sizeof(T)` is. – Dennis Zickefoose Nov 03 '11 at 22:22
  • *it looks like the compiler has no way of knowing what to use for sizeof(T)* - Of course it does; template implementations are all performed at compile time. The error message is clear; you need to cast the result of malloc. As to why you are using malloc at all... no idea. malloc will not call constructors for you. – Ed S. Nov 03 '11 at 22:23

3 Answers3

17

malloc returns a void*, and while C allowed incompatible pointers to be assigned, C++ does not. You need to cast to T* the result of malloc assuming arr is defined as T*.

arr = static_cast< T* >( malloc(10 * sizeof(T)) );

There is no problem in calling sizeof(T) within a template, as long as T is complete at the point of the instantiation (and int is a fundamental type, its always complete).

K-ballo
  • 80,396
  • 20
  • 159
  • 169
  • 2
    Working code: `arr = static_cast(malloc(10 * sizeof(T)));` – Pubby Nov 03 '11 at 22:22
  • Thank you! This took care of it. Thank you also for explaining this difference between C and C++. – Dom12 Nov 03 '11 at 22:32
  • If you use `malloc` in an idiomatic fashion, you don't actually need any casts, as you would use the resulting void pointer as the argument of a placement-new expression. The casting is a symptom of an ugly shortcut on part of the programmer that presumes PODness of the type. – Kerrek SB Nov 03 '11 at 23:12
  • @Kerrek SB: Since his intention is to use `realloc`, only POD types will work as elements of the set. But yes, its still ugly... – K-ballo Nov 03 '11 at 23:17
  • @K-ballo: OK, the general C++ answer to that would be to use a new `malloc()` plus copy/move plus `free()`... but that's forgoing the possible advantage that `realloc()` may not need to move anything around if the memory can be extended. (By the way, why doesn't C++ have a `renew` expression ;-).) *Edit:* A piecewise array allocator could simply check if the result of `realloc()` is different, and if not it could avoid the move altogether. – Kerrek SB Nov 03 '11 at 23:21
4

Of course you can. That's not the reason for the error you're having.

I'm guessing the set::arr member is of type T*. Because you've instantiated your set class with template parameter type int the declaration of that member variable becomes int *arr;. C++, unlike C, does not let you implicitly cast from a void * to another pointer type. So you'll need to cast the result of the malloc call.

arr = static_cast<T *>( malloc( 10 * sizeof(T) ) );

Also, remember that when you're actually inserting elements into the set you need to use placement new to construct the elements into the buffer you've allocated, and then explicitly invoke their destructors when copying / moving / removing them.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 1
    Presumably, the intent is not for an empty `set` to have ten elements, but to have *space* for ten elements... so long as he properly constructs the objects when they are inserted everything remains kosher. Of course, given the question at hand, the chances of that occurring are minimal. – Dennis Zickefoose Nov 03 '11 at 22:31
  • 1
    Also, `realloc` _can_ extend existing memory (sometimes) and it automatically copies the data over. So there _are_ benefits to realloc. Especially when working with PODs. – Mooing Duck Nov 03 '11 at 22:37
  • @DennisZickefoose Yep, you're right. I read `numElts = 0;` as `numElts = 10;`. I've updated the answer. – Praetorian Nov 03 '11 at 22:38
  • @MooingDuck You're right, I had no idea it could do that! But, as you've mentioned, that is only useful for POD types, otherwise it's just a waste of time. – Praetorian Nov 03 '11 at 22:44
  • @Praetorian: You can use it with non-PODs too, as long as you copy-construct/delete old _if_ realloc _does_ move. If realloc extends without moving, you can save many constructions. – Mooing Duck Nov 03 '11 at 22:46
  • @MooingDuck That's why I said *otherwise it's just a waste of time*; since it'll spend time doing a bitwise copy and you'll copy construct everything all over again on top of it. But now that I think about it, you don't even need to copy construct, just let realloc do its thing and then don't invoke the destructors for the existing buffer if realloc is not able to extend it. That should work, shouldn't it? – Praetorian Nov 03 '11 at 22:51
  • @Praetorian: if realloc fails to extend, it'll do the bitwise copy which is a waste of time any way you cut it. The question is, how often does it fail to extend? (I have no idea, it could be 100% for all I know) – Mooing Duck Nov 03 '11 at 22:55
  • @MooingDuck Exactly, it'll do a bitwise copy thus duplicating all existing elements. So, as long as you don't destruct the existing elements you won't run into double deletions or someone thinking they own a resource that someone else has released. Sounds screwy though, and with move construction / assignment there really is not reason to resort to this kinda stuff. – Praetorian Nov 03 '11 at 22:58
1

I think you're over-analyzing the problem. It's simply telling you that you need to cast the void* returned by malloc into the T* type of arr.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622