Математическая библиотека векторного заголовок шаблона на тип данных и размер


Я создал векторный библиотека заголовок математике, где размер и тип хранимых данных значений шаблона, и я хотел бы его рецензируемых пожалуйста. У меня есть несколько основных вопросов:

Будет через мой SizedVectorBase структуры предотвращения шаблон инстанцирования наворотов код, как я намеревался с ним?

Функции я определил пользователем правильнее сделать так, а я пропустила что-нибудь?

Как я могу определить константные векторы базиса, как Vector2 ноль(0,0) или UnitZ метода Vector3(0,0,1)?

Мне не хватает какой-либо функциональности, должна иметь вектор математическая библиотека?

Любая обратная связь приветствуется, спасибо.

#pragma once
#include <algorithm>
#include <type_traits>
#include <stdexcept>
#include <cmath>

// Base vector without size templated to avoid code-bloated binaries
template<typename T>
struct SizedVectorBase
{
public:
    T& operator[](std::size_t index)
    {
        if (index >= size)
        {
            throw std::out_of_range("Operator [] access out of bounds on Vector struct");
        }
        else
        {
            return pData[index];
        }
    }

    const T& operator[](std::size_t index) const
    {
        if (index >= size)
        {
            throw std::out_of_range("Operator [] access out of bounds on Vector struct");
        }
        else
        {
            return pData[index];
        }
    }

    // Component-wise vector +=
    SizedVectorBase<T>& operator+=(const SizedVectorBase<T>& rhs)
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] += rhs.pData[i];
        }
        return *this;
    }

    // Component-wise vector -=
    SizedVectorBase<T>& operator-=(const SizedVectorBase<T>& rhs)
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] -= rhs.pData[i];
        }
        return *this;
    }

    // Component-wise vector *=
    SizedVectorBase<T>& operator*=(const SizedVectorBase<T>& rhs)
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] *= rhs.pData[i];
        }
        return *this;
    }

    // Component-wise vector /=
    SizedVectorBase<T>& operator/=(const SizedVectorBase<T>& rhs)
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] /= rhs.pData[i];
        }
        return *this;
    }

    // Scalar *=
    template<typename S>
    SizedVectorBase<T>& operator*=(const S& scalar)
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] *= scalar;
        }
        return *this;
    }

    // Scalar /=
    template<typename S>
    SizedVectorBase<T>& operator/=(const S& scalar)
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] /= scalar;
        }
        return *this;
    }

    // Length squared of vec
    T LengthSq() const
    {
        T sum = 0;
        for (std::size_t i = 0; i < size; ++i)
        {
            sum += pData[i] * pData[i];
        }
        return sum;
    }

    // Length of vec
    T Length() const
    {
        return sqrt(LengthSq());
    }

    // Normalize this vector in place
    void Normalize()
    {
        *this /= Length();
    }

    // Dot product
    T Dot(const SizedVectorBase<T>& other) const
    {
        T sum = 0;
        for (std::size_t i = 0; i < size; ++i)
        {
            sum += pData[i] * other.pData[i];
        }
        return sum;
    }

    // Component-wise clamp values between 0 and 1 this vector in place
    void Saturate()
    {
        Clamp(0, 1);
    }

    // Component-wise clamp values between min and max this vector in place
    void Clamp(const T& min, const T& max)
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] = std::max(min, std::min(pData[i], max));
        }
    }

    // Component-wise absolute value this vector in place
    void Abs()
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] = abs(pData[i]);
        }
    }

protected:
    constexpr SizedVectorBase(std::size_t n, T* pMem)
        : size(n), pData(pMem)
    {}

    constexpr void LoopedCopyOtherRaw(const T* const otherRaw)
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            pData[i] = otherRaw[i];
        }
    }

private:
    std::size_t size;
    T* pData;
};

// Generic vector
template<typename T, std::size_t n>
struct Vector : public SizedVectorBase<T>
{
    T data[n];

    // Default constructor value initializes each element of data
    constexpr Vector()
        : data(), SizedVectorBase<T>(n, data)
    {}

    constexpr explicit Vector(const T& fillVal)
        : SizedVectorBase<T>(n, data)
    {
        std::fill(data, data + n, fillVal);
    }

    // enable_if has int member type iff Args has n elements, allowing substitution to succeed
    // enable_if has to have a default argument to follow a parameter pack
    template<typename... Args, typename std::enable_if<sizeof...(Args) == n, int>::type = 0>
    constexpr Vector(const Args&... args)
        : data{ args... }, SizedVectorBase<T>(n, data)
    {}

    constexpr Vector(T* rawOtherVec)
        : SizedVectorBase<T>(n, data)
    {
        LoopedCopyOtherRaw(rawOtherVec);
    }

    constexpr Vector(const Vector<T, n>& other)
        : SizedVectorBase<T>(n, data)
    {
        LoopedCopyOtherRaw(other.data);
    }

    constexpr Vector<T, n>& operator=(const Vector<T, n>& other)
    {
        LoopedCopyOtherRaw(other.data);
        return *this;
    }
};

// Vector2 template specialization
template<typename T>
struct Vector<T, 2> : public SizedVectorBase<T>
{
    union
    {
        T data[2];
        struct { T x, y; };
    };

    // Default constructor value initializes each element of data
    constexpr Vector()
        : data(), SizedVectorBase<T>(2, data)
    {}

    constexpr explicit Vector(const T& fillVal)
        : x(fillVal), y(fillVal), SizedVectorBase<T>(2, data)
    {}

    constexpr Vector(const T& inX, const T& inY)
        : x(inX), y(inY), SizedVectorBase<T>(2, data)
    {}

    constexpr Vector(T* rawOtherVec)
        : SizedVectorBase<T>(2, data)
    {
        LoopedCopyOtherRaw(rawOtherVec);
    }

    constexpr Vector(const Vector<T, 2>& other)
        : SizedVectorBase<T>(2, data)
    {
        LoopedCopyOtherRaw(other.data);
    }

    constexpr Vector<T, 2>& operator=(const Vector<T, 2>& other)
    {
        LoopedCopyOtherRaw(other.data);
        return *this;
    }
};

// Vector3 template specialization
template<typename T>
struct Vector<T, 3> : public SizedVectorBase<T>
{
    union
    {
        T data[3];
        struct { T x, y, z; };
        struct { T r, g, b; };
        Vector<T, 2> xy;
    };

    // Default constructor value initializes each element of data
    constexpr Vector()
        : data(), SizedVectorBase<T>(3, data)
    {}

    constexpr explicit Vector(const T& fillVal)
        : x(fillVal), y(fillVal), z(fillVal), SizedVectorBase<T>(3, data)
    {}

    constexpr Vector(const T& inX, const T& inY, const T& inZ)
        : x(inX), y(inY), z(inZ), SizedVectorBase<T>(3, data)
    {}

    constexpr Vector(T* rawOtherVec)
        : SizedVectorBase<T>(3, data)
    {
        LoopedCopyOtherRaw(rawOtherVec);
    }

    constexpr Vector(const Vector<T, 3>& other)
        : SizedVectorBase<T>(3, data)
    {
        LoopedCopyOtherRaw(other.data);
    }

    constexpr Vector<T, 3>& operator=(const Vector<T, 3>& other)
    {
        LoopedCopyOtherRaw(other.data);
        return *this;
    }

    Vector<T, 3> Cross(const Vector<T, 3>& other) const
    {
        return Vector<T, 3>(y * other.z - z * other.y,
            z * other.x - x * other.z,
            x * other.y - y * other.x);
    }
};

// Vector4 template specialization
template<typename T>
struct Vector<T, 4> : public SizedVectorBase<T>
{
    union
    {
        T data[4];
        struct { T x, y, z, w; };
        struct { T r, g, b, a; };
        Vector<T, 2> xy;
        Vector<T, 3> xyz;
        Vector<T, 3> rgb;
    };

    // Default constructor value initializes each element of data
    constexpr Vector()
        : data(), SizedVectorBase<T>(4, data)
    {}

    constexpr explicit Vector(const T& fillVal)
        : x(fillVal), y(fillVal), z(fillVal), w(fillVal), SizedVectorBase<T>(4, data)
    {}

    constexpr Vector(const T& inX, const T& inY, const T& inZ, const T& inW)
        : x(inX), y(inY), z(inZ), w(inW), SizedVectorBase<T>(4, data)
    {}

    constexpr Vector(const Vector<T,3>& vec3, const T& scalar)
        : x(vec3.x), y(vec3.y), z(vec3.z), w(scalar), SizedVectorBase<T>(4, data)
    {}

    constexpr Vector(T* rawOtherVec)
        : SizedVectorBase<T>(4, data)
    {
        LoopedCopyOtherRaw(rawOtherVec);
    }

    constexpr Vector(const Vector<T, 4>& other)
        : SizedVectorBase<T>(4, data)
    {
        LoopedCopyOtherRaw(other.data);
    }

    constexpr Vector<T, 4>& operator=(const Vector<T, 4>& other)
    {
        LoopedCopyOtherRaw(other.data);
        return *this;
    }
};

// Component-wise vector addition
template<typename T, std::size_t n>
Vector<T, n> operator+(const Vector<T, n>& lhs, const Vector<T, n>& rhs)
{
    Vector<T, n> temp(lhs);
    temp += rhs;
    return temp;
}

// Component-wise vector subtraction
template<typename T, std::size_t n>
Vector<T, n> operator-(const Vector<T, n>& lhs, const Vector<T, n>& rhs)
{
    Vector<T, n> temp(lhs);
    temp -= rhs;
    return temp;
}

// Component-wise vector multiplication
template<typename T, std::size_t n>
Vector<T, n> operator*(const Vector<T, n>& lhs, const Vector<T, n>& rhs)
{
    Vector<T, n> temp(lhs);
    temp *= rhs;
    return temp;
}

// Component-wise vector division
template<typename T, std::size_t n>
Vector<T, n> operator/(const Vector<T, n>& lhs, const Vector<T, n>& rhs)
{
    Vector<T, n> temp(lhs);
    temp /= rhs;
    return temp;
}

// Scalar multiplication
template<typename T, std::size_t n, typename S>
Vector<T, n> operator* (const Vector<T, n>& vec, const S& scalar)
{
    Vector<T, n> temp(vec);
    temp *= scalar;
    return temp;
}

// Scalar multiplication
template<typename T, std::size_t n, typename S>
Vector<T, n> operator* (const S& scalar, const Vector<T, n>& vec)
{
    Vector<T, n> temp(vec);
    temp *= scalar;
    return temp;
}

// Scalar division
template<typename T, std::size_t n, typename S>
Vector<T, n> operator/(const Vector<T, n>& vec, const S& scalar)
{
    Vector<T, n> temp(vec);
    vec /= scalar;
    return temp;
}

// Negate unary -
template<typename T, std::size_t n>
Vector<T, n> operator-(const Vector<T, n>& vec)
{
    Vector<T, n> temp(vec);
    temp *= -1;
    return temp;
}

// Length squared of vec
template<typename T, std::size_t n>
T LengthSq(const Vector<T, n>& vec)
{
    return vec.LengthSq();
}

// Length of vec
template<typename T, std::size_t n>
T Length(const Vector<T, n>& vec)
{
    return vec.Length();
}

// Normalize a copy of vec
template<typename T, std::size_t n>
Vector<T, n> Normalize(const Vector<T, n>& vec)
{
    Vector<T, n> temp(vec);
    temp.Normalize();
    return temp;
}

// Dot product
template<typename T, std::size_t n>
T Dot(const Vector<T, n>& lhs, const Vector<T, n>& rhs)
{
    return lhs.Dot(rhs);
}

// Cross product
template<typename T>
Vector<T, 3> Cross(const Vector<T, 3>& lhs, const Vector<T, 3>& rhs)
{
    return lhs.Cross(rhs);
}

// Lerp from a to b by f
template<typename T, std::size_t n>
Vector<T, n> Lerp(const Vector<T, n>& a, const Vector<T, n>& b, float f)
{
    return Vector<T, n>(a + f * (b - a));
}

// Component-wise clamp values between 0 and 1 a copy of vec
template<typename T, std::size_t n>
Vector<T, n> Saturate(const Vector<T, n>& vec)
{
    Vector<T, n> temp(vec);
    temp.Saturate();
    return temp;
}

// Component-wise clamp values between min and max a copy of vec
template<typename T, std::size_t n>
Vector<T, n> Clamp(const Vector<T, n>& vec, const T& min, const T& max)
{
    Vector<T, n> temp(vec);
    temp.Clamp(min, max);
    return temp;
}

// Component-wise absolute value a copy of vec
template<typename T, std::size_t n>
Vector<T, n> Abs(const Vector<T, n>& vec)
{
    Vector<T, n> temp(vec);
    temp.Abs();
    return temp;
}

// Common aliases
using float2 = Vector<float, 2>;
using float3 = Vector<float, 3>;
using float4 = Vector<float, 4>;
using int2 = Vector<int, 2>;
using int3 = Vector<int, 3>;
using int4 = Vector<int, 4>;
using double2 = Vector<double, 2>;
using double3 = Vector<double, 3>;
using double4 = Vector<double, 4>;


179
4
задан 28 марта 2018 в 08:03 Источник Поделиться
Комментарии
1 ответ

Правильность


  • Я не могу доказать это с верхней части моей головы, но я уверена, что ваш union/struct трюк-это неопределенное поведение, по крайней мере, если T не простой тип. Я позволю лингвисты идут на дно материи.

  • порядок инициализации data и n переменные отличается от порядка, в котором они появляются в конструкторе аргумент списка. Я не понимаю, как он может навредить тебе здесь, но компиляторы предупреждают об этом и исправляю код является лучшим способом, чтобы заставить замолчать предупреждения.

Интерфейс и читаемость

В какой-то момент, когда вы достигаете определенного количества строк кода, Вы могли бы хотеть рассмотреть разделение интерфейса и реализации. Под этим я подразумеваю:

class Vector {
Vector();
Vector& operator+=(const Vector& other);
}

Vector& Vector::operator+=(const Vector& other) { // implementation }

Это делает для более легкого чтения и оценки.

Отцепление интерфейс

На первый взгляд, вы не можете увидеть, какая функция отсутствует. Я уверен, что придет день, Вы будете нужен какой-то другой функционал, и вы поймете, что как огромный, как ваш интерфейс, невозможно распространить ее снаружи. Например, существует удобный способ, чтобы выполнить итерации по вектору: for (auto i : myvec) ... это невозможно, потому что вы не предоставили итераторы. Вы не можете использовать <algorithm> по той же причине.

Посмотрите на стандартные контейнеры: они предлагают памяти и управления доступом, больше ничего (std::string исключение); сочетание емкости и алгоритмы, которые не знают ничего друг о друге то, что делает стандартные библиотеки настолько мощным.

Вот почему вы должны, на мой взгляд, разобщают контейнер часть и часть деятельности. Как только вы начинаете думать, вы поймете, что ваши компиляции размеру контейнер уже существует: std::array.

std::array

Интересно попробовать и снижения разбухания кода. Но я не вижу, как это реально происходит. Я не могу представить себе программу, где можно использовать 20+ различных типов векторов. 2D и 3D графика, колориметрии и др. счет использования. В любом случае, опираясь на std::array вы уже пользуются чрезвычайно оптимизированных реализаций.

По этому вопросу -оптимизации-я считаю, что ваш код является довольно хороший, но могло бы быть немного улучшены. Вы могли бы constexprЭд многие другие функции. Существует также несколько тайных технику, чтобы ускорить вещи, которая называется loop unrolling, что вы можете моделировать на этапе компиляции размера контейнеров. Я не уверен, я рекомендовал бы его, потому что ваш компилятор, вероятно, сделать это лучше, чем вы, но было бы интересно попробовать его.

Идея в том, чтобы создать список индексов, а затем использовать разложения упаковки параметр:

template <std::size_t... Ns>
void print(const std::array<int, 5>& array, std::index_sequence<Ns...>) {
auto unused = { (std::cout << std::get<Ns>(array), 0)... };
}

unused это initializer_list содержащего столько же нулей, как есть NsС, что это вообще не интересно, но std::get<Ns>(array) была расширена также и печатать.

Развертывание цикла

Можно обобщить эту технику. Можно даже написать небольшой библиотеки во время компиляции развертывание циклов. Например, каждый скалярные операции может быть разложен на множители благодаря этому compile_time_map вспомогательная функция:

#include <array>
#include <iostream>

template <typename T, std::size_t N, typename Fn, std::size_t... Ns>
auto compile_time_map_impl(Fn&& fn, std::array<T, N>& array, std::index_sequence<Ns...>) {
[[maybe_unused]] // suppress the warning
auto _ = { (fn(std::get<Ns>(array)), 0)... };
return array;
}

template <typename T, std::size_t N, typename Fn>
auto compile_time_map(Fn&& fn, std::array<T, N>& array) {
return compile_time_map_impl(std::forward<Fn>(fn), array, std::make_index_sequence<N>());
}

template <typename T, std::size_t N, typename F

int main() {
auto test = std::array<int, 5>{1,2,3,4,5};
test = compile_time_map([](auto& n) { ++n; }, test);
for (auto i : test) std::cout << i << ' ';
// output: 2 3 4 5 6
}

3
ответ дан 28 марта 2018 в 03:03 Источник Поделиться