C++模板专业化冗余减少

C++ Template specialization redundancy reduction

本文关键字:冗余 专业化 C++      更新时间:2023-10-16

我想写我自己的Vector类模板,还想添加一些专业化,例如可以通过x/y/z访问组件的3D向量类型。

到目前为止,模板和专业化工作得很好,但问题是,专业化模板需要从基本模板进行大量复制/粘贴才能工作。我想减少这个数字。

这就是现在的样子:

template<class T, unsigned int dim>
class Vector;
template<class T, unsigned int dim>
Vector<T, dim> add(Vector<T, dim> const& lhs, Vector<T, dim> const& rhs)
{
    Vector<T, dim> tmp;
    for (unsigned int i = 0; i < dim; ++i)
    {
        tmp[i] = lhs[i] + rhs[i];
    }
    return tmp;
}
template<class T, unsigned int dim, class S>
Vector<T, dim> add(Vector<T, dim> const& lhs, S const& rhs)
{
    Vector<T, dim> tmp;
    for (unsigned int i = 0; i < dim; ++i)
    {
        tmp[i] = lhs[i] + rhs;
    }
    return tmp;
}
template<class T, unsigned int dim>
Vector<T, dim> operator+(Vector<T, dim> const& lhs, Vector<T, dim> const& rhs)
{
    return vectors::add(lhs, rhs);
}
template<class T, unsigned int dim, class S>
Vector<T, dim> operator+(Vector<T, dim> const& lhs, S const& rhs)
{
    return vectors::add(lhs, rhs);
}
template<class T, unsigned int dim>
class Vector
{
//...
protected:
    T values[dim] __attribute((aligned(16)));
public:
    template<class R, unsigned int fdim>
    friend Vector<R, fdim> operator+(Vector<R, fdim> const& lhs, Vector<R, fdim> const& rhs);
    template<class R, unsigned int fdim, class S>
    friend Vector<R, fdim> operator+(Vector<R, fdim> const& lhs, S const& rhs);
    template<class R, unsigned int fdim, class S>
    friend Vector<R, fdim> operator+(S const& lhs, Vector<R, fdim> const& rhs);
//...
//constructors, etc.
};
template<class T>
class Vector<T, 3>
{
//...
protected:
    T values[3] __attribute((aligned(16)));
public:
    T& x = values[0];
    T& y = values[1];
    T& z = values[2];
    //lots of copy-pasta :(
    template<class R, unsigned int fdim>
    friend Vector<R, fdim> operator+(Vector<R, fdim> const& lhs, Vector<R, fdim> const& rhs);
    template<class R, unsigned int fdim, class S>
    friend Vector<R, fdim> operator+(Vector<R, fdim> const& lhs, S const& rhs);
    template<class R, unsigned int fdim, class S>
    friend Vector<R, fdim> operator+(S const& lhs, Vector<R, fdim> const& rhs);
//...
//constructors, etc.
};

现在,我认为简单的解决方案是简单地将Vector3D定义为Vector模板的子类,如下所示:

template<class T>
class Vector3D: public Vector<T, 3>
{
//...
public:
    T& x = values[0];
    T& y = values[1];
    T& z = values[2];
    //no copy-pasta :)
//...
//constructors, etc.
};

由于模棱两可,这根本不起作用:

ambiguous overload for ‘operator+’ (operand types are ‘const vec3f {aka const math::vectors::Vector3D<float>}’ and ‘math::vectors::vec3f {aka math::vectors::Vector3D<float>}’)
../main.cpp:84:16: note: candidates are:
In file included from ../main.cpp:10:0:
../include/vector.hpp:720:16: note: math::vectors::Vector<T, dim> math::vectors::operator+(const math::vectors::Vector<T, dim>&, const math::vectors::Vector<T, dim>&) [with T = float; unsigned int dim = 3u]
 Vector<T, dim> operator+(Vector<T, dim> const& lhs, Vector<T, dim> const& rhs)
                ^
../include/vector.hpp:726:16: note: math::vectors::Vector<T, dim> math::vectors::operator+(const math::vectors::Vector<T, dim>&, const S&) [with T = float; unsigned int dim = 3u; S = math::vectors::Vector3D<float>]
 Vector<T, dim> operator+(Vector<T, dim> const& lhs, S const& rhs)
                ^
../include/vector.hpp:732:16: note: math::vectors::Vector<T, dim> math::vectors::operator+(const S&, const math::vectors::Vector<T, dim>&) [with T = float; unsigned int dim = 3u; S = math::vectors::Vector3D<float>]
 Vector<T, dim> operator+(S const& lhs, Vector<T, dim> const& rhs)

因此,模板替换似乎失败了,因为S也可以用新的Vector3D类替换,而它应该只处理标量。

因此,我试图通过为标量编写一个小包装类来解决这个问题,比如:

template<class T>
class ScalarType
{
public:
    T value;
    ScalarType() :
            value(0)
    {
    }
    ScalarType(T const& _v) :
            value(_v)
    {
    }
    ScalarType(ScalarType<T> const& rhs) :
            value(rhs.value)
    {
    }
    operator T&()
    {
        return value;
    }
    operator T() const
    {
        return value;
    }
};

并将S const& (l|r)hs的所有实例替换为ScalarType<S> const& (l|r)hs

这使得两边都有Vectors的运算符能够再次工作,但本应处理Vector Scalar运算的运算符仍然失败。

这一次是因为标量值必须显式为ScalarType类型,因为隐式转换不适用于模板替换。

那么,有没有什么方法可以让它发挥作用,或者我必须坚持复制粘贴代码?

此处完成部分模板专业化和CRTP。

maybe_has_z<Container, N>是一个将Container::z()转换为Container::operator[](2)的类,但仅当Container::size() >= 3

#include <array>
#include <iostream>
#include <algorithm>
//
// some boilerplate - note the different indecies
//
// define some concepts
template<class Container, std::size_t N, typename= void>
struct maybe_has_x{};
template<class Container, std::size_t N, typename = void>
struct maybe_has_y{};
template<class Container, std::size_t N, typename = void>
struct maybe_has_z{};
// specialise the concepts into (sometimes) concrete accessors
template<class Container, std::size_t N>
struct maybe_has_x<Container, N, std::enable_if_t<(N > 0)>>
{
    auto& x() const { return static_cast<const Container&>(*this)[0]; }
    auto& x() { return static_cast<Container&>(*this)[0]; }
};
template<class Container, std::size_t N>
struct maybe_has_y<Container, N, std::enable_if_t<(N > 1)>>
{
    auto& y() const { return static_cast<const Container&>(*this)[1]; }
    auto& y() { return static_cast<Container&>(*this)[1]; }
};
template<class Container, std::size_t N>
struct maybe_has_z<Container, N, std::enable_if_t<(N > 2)>>
{
    auto& z() const { return static_cast<const Container&>(*this)[2]; }
    auto& z() { return static_cast<Container&>(*this)[2]; }
};
// define our vector type
template<class T, std::size_t N>
struct Vector
: std::array<T, N>
, maybe_has_x<Vector<T, N>, N>   // include the maybe_ concepts
, maybe_has_y<Vector<T, N>, N>
, maybe_has_z<Vector<T, N>, N>
{
private:
    using inherited = std::array<T, N>;
public:
    Vector() : inherited {} {};
    Vector(std::initializer_list<T> il)
    : inherited { }
    {
        std::copy_n(il.begin(), std::min(il.size(), this->size()), std::begin(*this));
    }
    Vector(const inherited& rhs) : inherited(rhs) {}
public:
    using value_type = typename inherited::value_type;
    // offer arithmetic unary functions in class (example +=)
    // note that this allows us to add integers to a vector of doubles
    template<class Other, std::enable_if_t<std::is_convertible<value_type, Other>::value> * = nullptr>
    Vector& operator+=(const Vector<Other, N>&rhs) {
        auto lfirst = std::begin(*this);
        auto rfirst = std::begin(rhs);
        auto lend = std::end(*this);
        while (lfirst != lend) {
            *lfirst += *rfirst;
            ++lfirst;
            ++rfirst;
        }
        return *this;
    }
};
// offer binary arithmetic as free functions
template<class T, std::size_t N, class Other>
Vector<T, N> operator+(Vector<T, N> lhs, const Vector<Other, N>& rhs) {
    lhs += rhs;
    return lhs;
}
// offer some streaming capability
template<class T, std::size_t N>
std::ostream& operator<<(std::ostream& os, const Vector<T, N>& rhs) {
    auto sep = "";
    os << '[';
    for (auto& x : rhs) {
        os << sep << x;
        sep = ", ";
    }
    return os << ']';
}
// test
int main()
{
    auto a = Vector<double, 3> { 2.1, 1.2, 3.3 };
    auto b = a + a + Vector<int, 3> { 1, 1, 1 };
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << a.x() << ", " << a.y() << ", " << a.z() << std::endl;
    auto c = Vector<double, 2> { 4.4, 5.5 };
    std::cout << c << std::endl;
    std::cout << c.x() << std::endl;
    std::cout << c.y() << std::endl;
    // won't compile
    //    std::cout << c.z() << std::endl;
}

预期输出:

[2.1, 1.2, 3.3]
[5.2, 3.4, 7.6]
2.1, 1.2, 3.3
[4.4, 5.5]
4.4
5.5