0

I have a Button class that can either display an image or a line of text, which I am trying to use a template implement.

If it's text, then the template type is: const char*

If its an image, then the template type is: const wchar_t*

Here is the method that needs to differentiate between the two types:

template <typename T>
void Button<T>::draw(EasyGraphics* canvas)
{
    canvas->setBackColour(colour);

    if (mouseOver)
    {
        canvas->setPenColour(EasyGraphics::BLACK, 4);
    }
    else
    {
        canvas->setPenColour(EasyGraphics::BLACK, 2);
    }

    canvas->drawRectangle(Entity::GetX(), Entity::GetY(), Entity::getWidth(), Entity::getHeight(), true);
    canvas->setFont(20, L"");
    canvas->setTextColour(textColour);

    switch (typeid(T))
    {
        // Button has display text
        case typeid(const char*):
        {
            canvas->drawText(displayData, Entity::GetX() + textXOfset, Entity::GetY() + (Entity::getHeight() / 4) - 3);
            break;
        }
        // Button has display image
        case typeid(const wchar_t*):
        {
            canvas->drawBitmap(displayData, Entity::GetX() + textXOfset, Entity::GetY() + (Entity::getHeight() / 4) - 3, 60, 60, 0x0000FF00);
            break;
        }
    }
}

I cannot seem to get the switch at the bottom to function correctly. I am not sure if a switch is the best way to go about it. Any advice would be appreciated, cheers.

J. Whitehead
  • 451
  • 4
  • 21

3 Answers3

6

Use type traits (and if constexpr) instead of typeid. For example:

void g_char(const char*);
void g_wchar_t(const wchar_t*);

template <typename T>
void f(T)
{
    if constexpr (std::is_same_v<T, const char*>)
    {
        g_char(T());
    }
    else if constexpr (std::is_same_v<T, const wchar_t*>)
    {
        g_wchar_t(T());
    }
}

http://coliru.stacked-crooked.com/a/08e9e66ed5c776a4

The reason a switch doesn't work here is that the condition must be implicitly convertible to an integral type, and std::type_info (what typeid returns) isn't.

But then if you try keeping the typeid expressions, you have the problem of compile-time type checking when you try calling drawText or drawBitmap. Both calls must be valid unless you use constexpr branches (if constexpr). However, the conditions must then be compile-time constants, where typeid cannot be used. So instead, you can use type traits, and in particular, std::is_same.

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
  • I have just tried this and seems to work fine for const char*, but I am getting this error: Error C2664 'void EasyGraphics::drawBitmap(const wchar_t *,int,int,int,int,int)': cannot convert argument 1 from 'T' to 'const wchar_t *' with [ T=const char * ] Chess Game – J. Whitehead May 01 '20 at 11:38
  • If you go with `typeid(T) == typeid(const char*)` solution, it's better to use std::is_same instead and `constexpr if`. – tikkerey May 01 '20 at 11:46
  • @tikkerey Thanks I was about to do that. It is the correct solution because of the different calls done in OP's function. – Nelfeal May 01 '20 at 11:51
  • Should be `else if constexpr`. https://stackoverflow.com/questions/52356341/do-i-need-to-put-constexpr-after-else-if – tikkerey May 01 '20 at 12:04
  • @tikkerey Good to know. Looks like one of those things most compilers are tolerant about. – Nelfeal May 01 '20 at 12:14
  • Also add `else static_assert(false, "not implemented")`. – tikkerey May 01 '20 at 12:27
  • @tikkerey That depends on the use case. In OP's switch, there is no default. But sure, that's probably something you'd want to add. – Nelfeal May 01 '20 at 12:29
1

Do a template specialization:

template <typename T>
void DoDraw(EasyGraphics* canvas);

template<>
void DoDraw<char>(EasyGraphics* canvas)
{
  canvas->drawText(displayData, Entity::GetX() + textXOfset, Entity::GetY() + (Entity::getHeight() / 4) - 3);
}

template<>
void DoDraw<wchar_t>(EasyGraphics* canvas)
{
  canvas->drawBitmap(displayData, Entity::GetX() + textXOfset, Entity::GetY() + (Entity::getHeight() / 4) - 3, 60, 60, 0x0000FF00);
}

template <typename T>
void Button<T>::draw(EasyGraphics* canvas)
{
  ...
  DoDraw(canvas);
}

*check the template argument, it's pretty weird to have const char* and not just char.

tikkerey
  • 663
  • 1
  • 6
  • 7
0

You can go for typeid with a caution that it is not suitable for run time polymorphism. For example below.

#include <iostream>

template<class T>
void getType(T A) {
    std::cout<<typeid(A).name()<<"\n";
}

class Base {
};

class Derived : public Base {
};

int main(){
    getType(Base());
    getType(Derived());
    Derived *d = new Derived();
    getType(d);
    Base *b = d;
    getType(b);
    return 0;
}

Output:

4Base
7Derived
P7Derived
P4Base
Debashis Prusty
  • 393
  • 2
  • 6