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

时间:2016-07-23 11:44:13

标签: c++ templates implicit-conversion substitution

我想编写自己的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

这使得双方使用Vector的运算符再次工作,但是应该处理Vector-Scalar操作的运算符仍然失败。

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

那么,有什么方法可以让它完全起作用,还是我必须坚持使用复制粘贴代码?

1 个答案:

答案 0 :(得分:1)

使用部分模板专业化和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