3

I have defined a custom enum for convenience over a particular class, and it is now getting in the way for getting it processed by more general processes.

How should I perform this typecast?

// A templated value holder:
template <typename T>
struct Holder {
    T _value;
};

// A more general process..
template <typename T>
struct General {
    Holder<T> *holder;
};
// ..over integral types:
struct IntGeneral : General<int> {};
// Here is something interesting: I can tell that this process will work on
// any enum type. But I've found no way to make this explicit.

// Convenience for a particular case
typedef enum {One, Two, Three} Enum;
typedef Holder<Enum> Particular;


int main() { 

    Particular* a( new Particular { One } );
    IntGeneral ig { static_cast<Holder<int>*>(a) }; // compiler grumbles

    return EXIT_SUCCESS;
}

This is what I get:

error: invalid static_cast from type ‘Particular* {aka Holder<Enum>*}’ to type ‘Holder<int>*’

Is there a way I can keep the convenient Enum and get this code compiled?


EDIT: This turned out to be an XY problem. An answer to Y has been accepted here, and several discussed. X has been moved to another question.

iago-lito
  • 3,098
  • 3
  • 29
  • 54

5 Answers5

3

You could add a templated conversion function that would allow Holder<T> to be converted to Holder<U> if T is convertible to U and T is an enum:

template <typename T>
struct Holder {
    T _value;

    template <typename U,
              typename = std::enable_if_t<
                std::is_convertible<T, U>::value && 
                std::is_enum<T>::value 
              >>
    operator Holder<U>() const {
        return {_value};
    }
};

Potentially just the convertibility check is sufficient, depending on your use-case.

This will let you write:

Holder<int> i = Holder<Enum>{One};

But it wouldn't let you do the pointer conversion as a cast. You'd have to allocate a whole new Holder<int>* to store the Holder<Enum>*.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • .. which is too bad. I think it is because the problem lies higher in the chain: Since I can tell that `IntGeneral` will work on any enum type. Is there not a way I could define it as `EnumGeneral : General` instead? – iago-lito Aug 31 '15 at 14:50
  • @Iago-lito You can't just have a pointer to any kind of `Holder`. Not unless you do type erasure... like `struct HolderBase { virtual ~HolderBase() = default; }; template struct Holder : HolderBase { T _value; }` and just hold a `HolderBase*`. – Barry Aug 31 '15 at 14:58
3

Is it sufficient to replace:

IntGeneral ig { static_cast<Holder<int>*>(a) }; // compiler grumbles

With:

IntGeneral ig { new Holder<int>{a->_value} };

Since it is a struct, you can access the value. Personally I would use getters and setter, but as presented, that should do what you want.

Alternately

You could replace:

typedef Holder<Enum> Particular;

With:

typedef Holder<int> Particular;

Since the int can hold the Enum

Glenn Teitelbaum
  • 10,108
  • 3
  • 36
  • 80
  • Using `_value` is not a solution since it is private in real life, but redefining `typedef Holder Particular` does work fine. I'm accepting the answer as an answer for Y, then reformulate X in another question. Thank you :) – iago-lito Aug 31 '15 at 16:08
1

I think the simplest way to get this to work is to specialize Holder<Enum> so that it has an implicit conversion to Holder<Int>.

template<>
struct Holder<Enum> {
  Enum _value;
  operator Holder<Int>() const { return { _value }; }
};

This is going to make copies of the enum though, so this might not do exactly what you want depending on what goes in the General processes.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • Indeed, it might not. And this makes me think that the real problem is elsewhere: see my [comment](http://stackoverflow.com/a/32313829/3719101) to Barry. – iago-lito Aug 31 '15 at 14:52
  • A possible alternative is, instead of making the holder expose a public value, just make it expose `set` and `get` accessors to the private value? Then the enum would be able to pose polymorphically as an int, and it can just throw an exception if you try to set it to a bad value I guess. FWIW I don't really understand the purpose of the `Holder` template. If it's purpose is just to represent a value of type `T` then why not just use a value of type `T`? – Chris Beck Aug 31 '15 at 14:59
  • Of course, `Holder` is more complicated and its `_value` is private in reality. I do access it with something like a `set`, but then how to make the enum "pose polymorphically as an int" when giving the pointer to `ig`, like you say? – iago-lito Aug 31 '15 at 15:04
  • Your template could be like this `template class Holder { private: T _value; public: template U get() const { return _value; } template void set(const U & u) { _value = u; } };` and then when you have a `Holder` you can use `get()` to get it as an int. – Chris Beck Aug 31 '15 at 15:07
  • Mmh.. It already is kind'a like this. But this does not satisfy the compiler when I need to cast `Holder*` to `Holder*`. Or have I missed something? – iago-lito Aug 31 '15 at 15:12
  • Yeah so, I don't know why you want to do that. Casting a regular `Enum*` to an `int*` isn't a very good idea either. If you need true run-time polymorphism, I think you don't want to use templates, you want to use an abstract base class and virtual inheritance. (Or, a discriminated union like boost::variant) If you want static polymorphism, then use that, and make statically typed and sized containers if you have to using template voodoo, boost::fusion, whatever else. But if you literally want `Holder*` to be castable to `Holder*` I think this is just not what templates are for. – Chris Beck Aug 31 '15 at 15:17
  • I think you're right: maybe it is something else I need. Does my edit make things more clear? – iago-lito Aug 31 '15 at 15:23
  • To be honest, this is looking like an XY problem -- why don't you tell us what you are actually trying to do in detail? – Chris Beck Aug 31 '15 at 15:29
  • It does. Should I open a new, more specific question now I am aware of the real problem? – iago-lito Aug 31 '15 at 15:34
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88386/discussion-between-iago-lito-and-chris-beck). – iago-lito Aug 31 '15 at 15:40
0

The problem is that static_cast is not meant for casting pointers of different types. I suggest you to use reinterpret_cast, it should do the work. Also, you cannot instantiate object of type IntGeneral with a pointer.

Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
BlackCat
  • 173
  • 7
  • 4
    Using `reinterpret_cast` here will result in undefined behavior. – Captain Obvlious Aug 31 '15 at 14:43
  • Captain, casting one pointer type to another is not a safe operation by default, but that's the sole (well, with some remarks) purpose of reinterpret_cast. :) – BlackCat Aug 31 '15 at 14:47
  • Would this not rely on the assumption that `Particular` has the same size / internal data organization as any `Holder`? If yes, then here it is not the case, because in reality `Particular` is more specialized and has specific members. I haven't precised this in the question though, not too make things complicated. – iago-lito Aug 31 '15 at 15:00
  • Yes, it does. It will cause problems if this is the case. – BlackCat Aug 31 '15 at 15:06
0

There are some several changes you can make:

  1. Add a parent type to the 'Holder' that can reference both generic and Particular types.
  2. Define a constructor to the Holder that receives T
  3. Define a constructor to the General that receives Holder address type
  4. And for the initialization list of IntGeneral (this is a weird name because it's not general but I get the point) Initiate with a "Holder*" address.

    struct HolderBase {};
    
    template <typename T>
    struct Holder : HolderBase{
         Holder(T value){ _value = value; }
         T _value;
    };
    
    template <typename T>
    struct General {
        General(Holder<T>* h){ holder = h; }
        Holder<T>* holder;
    };
    
    struct IntGeneral : General<int> {
        IntGeneral(Holder<int>* holder) : General<int>(holder){}
    };
    
    typedef enum { One, Two, Three } Enum;
    typedef Holder<Enum> Particular;
    
    
    int main() {
    
       HolderBase* a(new Particular{ One });
       IntGeneral ig{ static_cast<Holder<int>*>(a) };
    
       return 0;
    }
    

hope this is what you were looking for

Danny Mor
  • 1,143
  • 12
  • 12