2016-07-23 7 views
1

Я хотел написать свой собственный шаблон класса Vector, а также хотел бы добавить некоторые специализации, например трехмерный векторный тип, к которому к компонентам можно получить доступ через x/y/z.C++ Уменьшение резервирования шаблонов для шаблонов

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

Это то, что это выглядит как прямо сейчас:

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-Scalar, еще не сработали.

На этот раз это связано с тем, что скалярное значение должно быть явно типа ScalarType, так как неявные преобразования к ним не работают с заменой шаблона.

Итак, есть ли способ заставить это работать вообще или мне нужно придерживаться кода копирования-вставки?

+0

Вот возможная реализация http://stackoverflow.com/questions/31449433/ generic-c-многомерные-итераторы/38510047 # 38510047 – slavanap

+0

Вы делаете слишком много. Имейте вектор базового класса (value tuple) и реализуйте операции с циклами (используя параметр шаблона «dim»). Тогда (только если производительность и (!) Компилятор, сгенерированный сборкой, недостаточно хороши), настройте специализированные шаблоны. В любом случае, сохраняйте специализированную функциональность в специализированных шаблонах (не загромождайте базовый класс) –

+0

@ DieterLücking хорошо, это план. Операторы не специализируются вообще, просто сам векторный класс, и только потому, что я хочу, чтобы векторные компоненты были доступны через простые ссылки, такие как Vector3D test; test.x = 12; Но когда я просто специализируюсь на обычном способе, я должен либо переопределить каждую перегрузку оператора, либо использовать функции друзей, как указано выше. –

ответ

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 
+0

Ух, если я что-то не упустил, как это решает указанную проблему? –

+0

@pfannkuchen_gesicht о да, неверно истолковал вопрос как многомерные векторы. Извинения. обновит –

+0

@pfannkuchen_gesicht с правильным ответом. Мои извинения. –