How to create a function that converts a & lt; T & gt; In Class & lt; U & gt; where T and U are arbitrary number types?

advertisements

I'm working with SFML, and I'd like to do things like the following:

sf::Texture myTexture;
myTexture.loadFromFile("texture.png");

sf::Sprite mySprite(myTexture);
mySprite.setOrigin(myTexture.getSize() / 2.f); // <~-- error

The problem is that sf::Texture::getSize() returns a sf::Vector2<unsigned>, whilst sf::Transformable::setOrigin() expects for a sf::Vector2<float>, and they're not convertible into each other.

I thought on creating a function that would accept any sf::Vector2<T> and return an equivalent sf::Vector<U>, where T and U are both arbitrary number types, so I could rewrite that last line of code as:

// converting from 'unsigned' to 'float'
mySprite.setOrigin(toVector2<unsigned, float>(myTexture.getSize()) / 2.f);

What I have so far is the following, but it doesn't work.

// Util.h
namespace smi
{
    template <typename FROM, typename TO>
    sf::Vector2<TO> toVector2(const sf::Vector2<FROM> &other)
    {
        return sf::Vector2<TO>(
            static_cast<TO>(other.x),
            static_cast<TO>(other.y));
    }
}

// then ...
mySprite.setOrigin(toVector2<unsigned, float>(myTexture.getSize()) / 2.f);
// ^ no instance of function template "toVector2" matches the argument list

Here's an image showing the error:

How could I achieve such a generic conversion?


In general, you can't do this for arbitrary types. There is not necessarily any relationship between two different specializations of a class template, and no generic way to convert from one to the other.

For specific cases you can usually do something like:

template <typename To, typename From>
  Thing<To> convert(const Thing<From>& from)
  {
    return Thing<To>( /* internal value(s) of from */ );
  }

i.e. construct a new object from the value(s) held in the source object. But to do that you need to know the API of the type, so that you can get the values out and call an appropriate constructor e.g.

template <typename To, typename From>
  std::complex<To> convert_complex(const std::complex<From>& from)
  {
    return std::complex<To>( from.real(), from.imag() );
  }

or:

template <typename To, typename From>
  std::vector<To> convert_vector(const std::vector<From>& from)
  {
    return std::vector<To>( from.begin(), from.end() );
  }

or:

template <typename To, typename From>
  std::shared_ptr<To> convert_shared_ptr(const std::shared_ptr<From>& from)
  {
    return std::dynamic_pointer_cast<To>(from);
  }

But there is no way to do that completely generically, as every type has a different API for getting to its internal values (if it's even possible) and for constructing a new object.

Your toVector2 function looks like it should work though, as it does exactly this. As Pete pointed out, the error shows you have not defined your toVector2 function as shown in your question: you put the arguments the other way around!

N.B. you can re-order the template parameters so that you can use template argument deduction to simplify your conversion function:

template <typename To, typename From>
  sf::Vector2<To> toVector2(const sf::Vector2<From>& other)
  {
    return ...
  }

Now you can just call toVector2<float>(myTexture.getSize()) and the From argument will be deduced as unsigned.

Since that seems to be how you defined the real function anyway, just change the code to toVector2<float>(myTexture.getSize()) and it should work.