Создать 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, даже в пределах typedefs.

Итак, когда у меня есть код, который должен серьезно работать с многомерными массивами, я серьезно спрашиваю себя, стоит ли перемещать этот код в чистый .файл c и просто свяжи это с остальным моим проектом.