Создать n-мерный вектор с заданными размерами
Итак, я хочу создать многомерный вектор данного типа, где первое измерение будет иметь размер первого аргумента вызова функции и т. д., Например, если я сделаю
std::size_t n = 5;
auto x = make_vector<int>(n + 1, n * 2, n * 3);
x
должен быть массив 6x10x15 3d (состоящий из нулей, потому что я хочу построить по умолчанию прямо сейчас)
Я попытался это:
template <typename T>
std::vector<T> make_vector(std::size_t size) {
return std::vector<T>(size);
}
template <typename T, typename... Args>
auto make_vector(std::size_t first, Args... sizes) -> std::vector<decltype(make_vector<T>(sizes...))> {
auto inner = make_vector<T>(sizes...);
return std::vector<decltype(inner)>(first, inner);
}
кажется, что он работает для 1 или 2 аргументов, но не работает для 3 аргументов со следующими ошибка(лязг++)
In file included from /Users/riad/ClionProjects/for-jhelper/output/run.cpp:1:
/Users/riad/ClionProjects/for-jhelper/tasks/TaskC.cpp:12:12: error: no matching function for call to 'make_vector'
auto x = make_vector<int>(n + 1, n * 2, n * 3);
^~~~~~~~~~~~~~~~
/Users/riad/ClionProjects/for-jhelper/tasks/../spcppl/make_vector.hpp:9:6: note: candidate template ignored: substitution failure [with T = int, Args = <unsigned long, unsigned long>]: call to function 'make_vector' that is neither visible in the template definition nor found by argument-dependent lookup
auto make_vector(std::size_t first, Args... sizes) -> std::vector<decltype(make_vector<T>(sizes...))> {
^ ~~~~~~~~~~~
/Users/riad/ClionProjects/for-jhelper/tasks/../spcppl/make_vector.hpp:4:16: note: candidate function template not viable: requires single argument 'size', but 3 arguments were provided
std::vector<T> make_vector(std::size_t size) {
если я правильно понимаю, проблема в том, что когда компилятор пытается вычислить возвращаемое значение make_vector, он должен знать возвращаемое значение вектора с меньшим количеством аргументов и не может этого сделать. Как это исправить?
5 ответов
создайте пространство имен, чтобы поместить в него помощников, называемых details
.
на details
создать типа struct adl_helper{};
создать реализацию make_vector
, за исключением его первого параметра всегда параметр шаблона называется Adl
. Этот параметр шаблона никогда не называется, и его экземпляры передаются рекурсии.
реализовать make_vector( blah )
по телефону details::make_vector<T>( details::adl_helper{}, blah )
.
namespace details {
struct adl_helper { };
template <class T, class Adl>
std::vector<T> make_vector(Adl, size_t size) {
return std::vector<T>(size);
}
template <class T, class Adl, class... Args,
class R_T=decltype(
make_vector<T>(Adl{}, std::declval<Args>()...)
),
class R=std::vector<R_T>
>
R make_vector(Adl, size_t first, Args... sizes)
{
auto inner = make_vector<T>(Adl{}, std::forward<Args>(sizes)...);
return R(first, inner);
}
}
template <class T, class... Args,
class R=decltype(
details::make_vector<T>(details::adl_helper{}, std::declval<Args>()...)
)
>
R make_vector(Args... args)
{
return details::make_vector<T>(details::adl_helper{}, std::forward<Args>(args)...);
}
то, что здесь происходит, - это, казалось бы, рекурсивный вызов в сигнатура функции шаблона оценивается в двух контекстах.
во-первых, он оценивается там, где он объявлен. В частности, он оценивается перед собой определился. Поэтому он не" ловит " себя.
во-вторых, ADL (поиск, зависящий от аргумента) выполняется в точке, где он создается на основе только типов шаблонов, переданных функции.
на template<class Adl>
и adl_helper
types означает, что этот аргумент зависимый поиск может видеть details::make_vector
сама функция, когда она создается. И когда он ищет тип возврата, он может и посмотреть details::make_vector
. Так далее.
видео.
использование class R=
псевдонимы и тому подобное просто есть, чтобы очистить код и уменьшить ненужное дублирование.
в этом конкретном случае усилия, необходимые для создания возвращаемого типа, проще, чем все это гимнастика.
я думаю это решение чище:
template<class T>struct tag{using type=T;};
template<class Tag>using type=typename Tag::type;
template<class T, size_t n>
struct n_dim_vec:tag< std::vector< type< n_dim_vec<T, n-1> > > > {};
template<class T>
struct n_dim_vec<T, 0>:tag<T>{};
template<class T, size_t n>
using n_dim_vec_t = type<n_dim_vec<T,n>>;
интересный вопрос! Проблема, с которой вы сталкиваетесь, заключается в том, что неквалифицированный поиск имени будет выглядеть в областях, где он используется (в порядке возрастания общности). Но, из [основных.масштаб.pdecl]:
на пункт декларации для имени сразу после его полного Декларатора (пункт 8) и перед его инициализатор (если есть)
и в этой функции:
template <typename T, typename... Args>
auto make_vector(std::size_t first, Args... sizes)
-> std::vector<decltype(make_vector<T>(sizes...))> {
...
}
"полное Декларатор " включает тип trailing-return. Итак, шаблон функции make_vector<T, Args...>
не будет в области еще до {
. Вот почему это не будет компилироваться для Аргументов 3+.
на простой fix было бы, если бы у вас был доступ к C++14, вам просто не нужен тип трейлинга;
template <typename T, typename... Args>
auto make_vector(std::size_t first, Args... sizes)
{ /* exactly as before */ }
в теле функции name вы можете сделать рекурсивный вызов без проблем.
без C++14 вы можете переслать его в класс шаблон, имя которого будет находиться в области всех рекурсивных вызовов:
template <typename T, size_t N>
struct MakeVector
{
template <typename... Args>
static auto make_vector(std::size_t first, Args... sizes)
-> std::vector<decltype(MakeVector<T, N-1>::make_vector(sizes...))>
{
auto inner = MakeVector<T, N-1>::make_vector(sizes...);
return std::vector<decltype(inner)>(first, inner);
}
};
template <typename T>
struct MakeVector<T, 1>
{
static std::vector<T> make_vector(std::size_t size) {
return std::vector<T>(size);
}
};
template <typename T, typename... Args>
auto make_vector(Args... args)
-> decltype(MakeVector<T, sizeof...(Args)>::make_vector(args...))
{
return MakeVector<T, sizeof...(Args)>::make_vector(args...);
}
мне удается сделать это, вычисляя тип отдельно, но это кажется ненужным, несмотря на то, что он довольно короткий.
template <typename T, int n>
struct NDVector {
typedef std::vector<typename NDVector<T, n - 1>::type> type;
};
template <typename T>
struct NDVector<T, 0> {
typedef T type;
};
template <typename T>
std::vector<T> make_vector(std::size_t size) {
return std::vector<T>(size);
}
template <typename T, typename... Args>
typename NDVector<T, sizeof...(Args) + 1>::type make_vector(std::size_t first, Args... sizes) {
typedef typename NDVector<T, sizeof...(Args) + 1>::type Result;
return Result(first, make_vector<T>(sizes...));
}
действительно оценит более элегантное решение
Я не знал о других ответах, когда я опубликовал это. Не удаляя его в случае, если он может быть кому-то полезен. Конечно, решение довольно тривиально с включенным C++14.
С [код](демо - @ideone вы можете добиться:
size_t n = 5;
auto x = make_vector<int>(n+1, n*2, n*3);
вот код с минимальными комментариями:
// Creating a multidimensional container (e.g. `vector`, `map`)
template<template<typename...> class Container,
typename T,
size_t DIMENSION>
struct MultiDimensional
{
using internal = MultiDimensional<Container, T, DIMENSION-1>;
using type = Container<typename internal::type>;
template<typename... Args>
static
type Generate (const size_t size, Args... sizes)
{
return type(size, internal::Generate(sizes...));
}
};
// Last dimension overload
template<template<typename...> class Container,
typename T>
struct MultiDimensional<Container, T, 1>
{
using internal = T;
using type = Container<T>;
static
type Generate (const size_t size)
{
return type(size);
}
};
// Wrapper for the specific requirement of creating a multidimensional `std::vector`
template<typename T,
typename... Args>
auto make_vector(Args... sizes)
-> typename MultiDimensional<std::vector, T, sizeof...(sizes)>::type
{
return MultiDimensional<std::vector, T, sizeof...(sizes)>::Generate(sizes...);
}
самое простое решение этого-вернуться к C:
void foo(size_t n) {
int (*threeDArray)[2*n][3*n] = malloc((n + 1)*sizeof(*threeDArray));
//Do with your array whatever you like.
//Here I just initialize it to zeros:
for(size_t i = 0; i < n + 1; i++) {
for(size_t j = 0; j < 2*n; j++) {
for(size_t k = 0; k < 3*n; k++) {
threeDArray[i][j][k] = 0;
}
}
}
free(threeDArray);
}
Как я уже сказал, это невозможно в C++: стандарт C++ требует, чтобы все размеры массива были константами времени компиляции. C гораздо более гибок в этом отношении, позволяя размеры массива времени выполнения везде с C99, даже в пределах typedef
s.
Итак, когда у меня есть код, который должен серьезно работать с многомерными массивами, я серьезно спрашиваю себя, стоит ли перемещать этот код в чистый .файл c и просто свяжи это с остальным моим проектом.