Search code examples
c++templatesc++17variadic-templatesinitializer-list

Cannot convert from initializer_list to my type, which has templated variadic constructor


So, this isn't really something I have to do, I was just playing around. I wrote a Vector class for vectors of any numeric type and any number of coordinates. It is used as Vector<NumericType, [num of coords]>. Here is the code:

#include <array>
#include <functional>

namespace World {

template <typename NumType, unsigned char Size>
class Vector
{
public:
  using CoordType = NumType;


  template<typename... NumTypes>
  constexpr Vector(NumTypes&&... vals) : values{ std::forward<NumTypes>(vals)... }
  {
    static_assert(sizeof...(NumTypes) == Size, "You must provide N arguments.");
  }

  Vector(const std::array<NumType, Size>& values) : values(values) {}
  Vector(const std::array<NumType, Size>&& values) : values(std::move(values)) {}

  const NumType& operator[](size_t offset) const { return values[offset]; }
  NumType& operator[](size_t offset) { return values[offset]; }

  //! Converts all values to new given type
  template <typename NewType>
  constexpr Vector<NewType, Size> Convert() const { return Convert<NewType>(std::make_index_sequence<Size>{}); }
  //! Converts all values via the conversion function
  template <typename NewType>
  Vector<NewType, Size> Convert(const std::function<NewType(NumType)>& callback) const { return Convert<NewType>(std::make_index_sequence<Size>{}, callback); }

  std::array<NumType, Size> values;

private:
  //! Converts all values to new given type
  template <typename NewType, std::size_t ... Is>
  constexpr Vector<NewType, Size> Convert(std::index_sequence<Is...>) const { return { { static_cast<NewType>(values[Is])}... }; }
  //! Converts all values via the conversion function
  template <typename NewType, std::size_t ... Is>
  Vector<NewType, Size> Convert(std::index_sequence<Is...>, const std::function<NewType(NumType)>& callback) const { return { { callback(values[Is])}... } ; }
};

Now what I am trying to do now is to make sure that the conversion function declared above is working. The idea of usage is this:

using namespace World;
using Vector3D = Vector<double, 3>;
using Vector3Int = Vector<int, 3>;

#include <cmath>

int main()
{
  const Vector3D src{ 1.4, 2.5, 3.6 };
  const Vector3Int target = src.Convert<int>([](double val) { return (int)std::round(val); });
  return 0;
}

The issue here is that when the conversion function is compiled, the resulting new values appear in the form of std::initializer_list<NewType>. For some reason, this doesn't qualify for the constructor Vector(NumTypes&&... vals). Now I do not want to have an initializer list constructor - the number of expected arguments is not variable, it must be whatever the Size template parameter says.

So how to get around this? How can I convert std::initializer_list to whatever NumTypes&&... vals is?

I must admit here I do not precisely know what I'm doing, I am trying to improve my C++ knowledge.


Solution

  • In Convert, you have the parameter pack Is which you'd like to use in conjunction with callback and values.

    Parameter pack expansion "expands to comma-separated list of zero or more patterns", so this is what you'll get when used with the values in your question:

    Is...

    0, 1, 2
    

    values[Is]... => values[0], values[1], values[2]

    1.4, 2.5, 3.6
    

    callback(values[Is])... => callback(values[0]), callback(values[1]), callback(values[2])

    1, 3, 4
    

    And you want to create a braced-init-list like this:

    {callback(values[0]), callback(values[1]), callback(values[2])}
    // =>
    {1, 3, 4}
    

    So:

    template <typename NewType, std::size_t... Is>
    Vector<NewType, Size> 
            Convert(std::index_sequence<Is...>, 
                    const std::function<NewType(NumType)>& callback) const
    {
        return {callback(values[Is])...};
    }