Я хотел бы написать шаблонную функцию, которая выполняет математические вычисления для контейнеров (математических) векторов. Мне нужна гибкость в аргументе шаблона T, чтобы он мог быть std::array
или std::vector
или std::list
. Эти типы имеют очень похожие интерфейсы, с основным отличием в том, что std::array
является типом фиксированного размера, тогда как std::list
и std::vector
могут быть динамически изменены (хотя и с различными моделями памяти).
Для ради этого примера давайте предположим, что внешний контейнер всегда std::vector
и что функция возвращает вновь созданный вектор Ts:
#include <vector>
#include <array>
#include <iostream>
using std::cout;
using std::endl;
template<typename T>
std::vector<T>
process_vectors (const std::vector<T>& vecs) {
// Create the return object with the same numbers of T objects it is as vecs:
std::vector<T> rtn (vecs.size());
// The missing code, if T is std::vector, is to resize each member of rtn to
// have the same size as each element of vecs. What's the best way to introduce
// this small difference between the implementations?
// Use iterators to work through vecs and rtn, as these are a common interface to
// all the STL containers
typename std::vector<T>::const_iterator vi = vecs.begin();
typename std::vector<T>::iterator ri = rtn.begin();
// For this example, let's just copy the input to the output, copying each T
// element by element:
while (vi != vecs.end()) {
// For each mathematical vector in vecs, loop through its vector components:
typename T::const_iterator vi_i = vi->begin();
// And copy the result into the return's components:
typename T::iterator ri_i = ri->begin();
while (vi_i != vi->end()) {
// The 'operation' of this function; performing a copy
// (My real code would do something more useful, like
// auto-scaling the lengths of the vectors)
*ri_i = *vi_i;
// On to the next elements:
++vi_i;
++ri_i;
}
++vi;
++ri;
}
return rtn;
}
int main() {
// vector<array> version
std::vector<std::array<float, 3>> the_vecs(3);
the_vecs[0] = { 1.0f, 1.0f, 1.0f };
the_vecs[1] = { 2.0f, 2.0f, 2.0f };
the_vecs[2] = { 3.0f, 3.0f, 3.0f };
std::vector<std::array<float, 3>> rtn_obj = process_vectors (the_vecs);
cout << "vector<array> rtn_obj size is " << rtn_obj.size() << endl;
// vector<vector> version will crash
std::vector<std::vector<float>> the_vecs2;
the_vecs2.push_back ({ 1.0f, 1.0f, 1.0f });
the_vecs2.push_back ({ 2.0f, 2.0f, 2.0f });
the_vecs2.push_back ({ 3.0f, 3.0f, 3.0f });
std::vector<std::vector<float>> rtn_obj2 = process_vectors (the_vecs2);
cout << "vector<vector> rtn_obj2 size is " << rtn_obj2.size() << endl;
return 0;
}
Если вы скомпилируете и запустите этот пример, первая строфа в main()
будет работать нормально, а второй (vector<vector>
один) обработает sh с ошибкой памяти, поскольку элементы rtn
не выделены. Итак, насколько я могу видеть, мне нужно создать две версии этой функции, одну с изменением размера для vector<list>
или vector<vector>
типизированных аргументов, и одну без изменения размера для аргументов, набранных как vector<array<float, 3>>
.
Теперь я знаю, как тестировать во время компиляции другой тип T
, чтобы я мог написать две отдельные реализации этой функции, в зависимости от того, T
равен array
или vector
. Я использую этот код:
#include <array>
#include <list>
#include <type_traits>
#include <utility>
#include <vector>
#include <type_traits>
#include <iostream>
using std::cout;
using std::endl;
// specialize a type for resizable stl containers
namespace is_resizable_vector_impl {
template <typename T> struct is_resizable_vector:std::false_type{};
template <typename... Args> struct is_resizable_vector<std::vector <Args...>>:std::true_type{};
template <typename... Args> struct is_resizable_vector<std::list <Args...>>:std::true_type{};
// etc, other types omitted
}
// I've omitted a similar test for fixed-size stl containers (i.e. std::array)
// From the typename T, set a value attribute which says whether T is a scalar (like
// float, double), a resizable list-like type (std::vector, std::list etc) or
// a fixed-size list-like type, such as std::array.
template <typename T>
struct number_type {
static constexpr bool const scalar = std::is_scalar<std::decay_t<T>>::value;
static constexpr bool const resizable = is_resizable_vector_impl::is_resizable_vector<std::decay_t<T>>::value;
// 0 default value 0 for default impl (vector-common)
// 1 scalar == false and resizable == true => value 1 for resizable vector implementations
// 2 scalar == false and resizable == false => value 2 for fixed-size vector implementations
// 3 scalar == true => value 3 for scalar
static constexpr int const value = scalar ? 3 : (resizable ? 1 : 2);
};
// Common/default implementation
template <int vtype = 0>
struct Implementation
{
template<typename T>
static std::vector<T> process_vectors (const std::vector<T>& vecs) {
std::vector<T> rtn (vecs.size());
// common/default implementation if possible...
return rtn;
}
};
// Resizable (T is std::vector or std::list) implementation
template <>
struct Implementation<1>
{
template<typename T>
static std::vector<T> process_vectors (const std::vector<T>& vecs) {
std::vector<T> rtn (vecs.size());
cout << "resizable T implementation with .resize()s" << endl;
return rtn;
}
};
// Fixed-size (T is std::array) implementation
template <>
struct Implementation<2>
{
template<typename T>
static std::vector<T> process_vectors (const std::vector<T>& vecs) {
std::vector<T> rtn (vecs.size());
cout << "fixed-size T implementation WITHOUT .resize()s" << endl;
return rtn;
}
};
// Scalar implementation omitted; it's outside the scope of this stackoverflow question
// Now I can write out
template<typename T>
std::vector<T> process_vectors (const std::vector<T>& vecs) {
return Implementation<number_type<T>::value>::process_vectors (vecs);
}
int main ()
{
// vector<array> version
std::vector<std::array<float, 3>> the_vecs(3);
the_vecs[0] = { 1.0f, 1.0f, 1.0f };
the_vecs[1] = { 2.0f, 2.0f, 2.0f };
the_vecs[2] = { 3.0f, 3.0f, 3.0f };
std::vector<std::array<float, 3>> rtn_obj = process_vectors (the_vecs);
// vector<vector> version
std::vector<std::vector<float>> the_vecs2;
the_vecs2.push_back ({ 1.0f, 1.0f, 1.0f });
the_vecs2.push_back ({ 2.0f, 2.0f, 2.0f });
the_vecs2.push_back ({ 3.0f, 3.0f, 3.0f });
std::vector<std::vector<float>> rtn_obj2 = process_vectors (the_vecs2);
return 0;
}
Проблема в том, что мне все еще приходится дублировать довольно много кода (с копией функции в struct Implementation<1>
и другой в struct Implementation<2>
), даже хотя единственная разница здесь заключается в необходимости изменения размера каждого элемента rtn
. Итак, вопрос «как бы вы избежали дублирования функции process_vectors?»
Спасибо за чтение!