【问题标题】:Create n-dimensional vector with given sizes创建具有给定大小的 n 维向量
【发布时间】:2015-05-01 21:47:37
【问题描述】:

所以,我想要创建给定类型的多维向量,其中第一个维度将具有函数调用的第一个参数的大小等,例如,如果我这样做

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 个参数失败并出现以下错误(clang++)

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 的返回值时,它必须知道带有较少参数的向量的返回值,并且无法这样做。我该如何解决?

【问题讨论】:

  • @Columbo,你能详细说明一下吗?
  • 您是否认真考虑过vector&lt;vector&lt;&gt;&gt;?如果您需要多维数组,请使用一维数组并将其包装起来。
  • 哦,不知道,性能大幅提升?减少不必要的间接?技术上强制边界一致性?
  • vector> 与其说是多维数组,不如说是一个列表列表。
  • @Columbo:手动编写的多维数组仍然是错误的:std::array&lt;std::array&lt;T, N1&gt;, N2&gt; 是在固定边界的情况下要走的路。

标签: c++ templates c++11 variadic-templates template-meta-programming


【解决方案1】:

有趣的问题!您遇到的问题是不合格的名称查找将在使用它的范围内查找(按一般性递增的顺序)。但是,来自 [basic.scope.pdecl]:

名称的声明点紧接在其完整声明符之后(第 8 条)和其之前 初始化器(如果有)

在这个函数中:

template <typename T, typename... Args>
auto make_vector(std::size_t first, Args... sizes) 
-> std::vector<decltype(make_vector<T>(sizes...))> {
    ...
}

“完整声明符”包括尾随返回类型。所以函数模板make_vector&lt;T, Args...&gt; 直到{ 才会在范围内。这就是为什么这不会为 3 个以上的参数编译。

最简单的解决方法是,如果您可以访问 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...);
}

【讨论】:

    【解决方案2】:

    创建一个命名空间以将一些助手放入其中,称为details

    details 中创建一个类型struct adl_helper{};

    创建make_vector 的实现,除了它的第一个参数始终是一个名为Adl 的模板参数。这个模板参数永远不会被命名,它的实例被传递给递归。

    通过调用details::make_vector&lt;T&gt;( details::adl_helper{}, blah )来实现make_vector( 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&lt;class Adl&gt;adl_helper 类型意味着这个依赖于参数的查找可以在实例化时看到 details::make_vector 函数本身。当它查找返回类型时,它可以看到details::make_vector。等等。

    live example.

    使用class R= 别名等只是为了清理代码并减少一些不必要的重复。

    在这种特殊情况下,构建返回类型所需的工作比所有这些体操都容易。

    我认为this solution is cleaner

    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>>;
    

    【讨论】:

    • 你能详细说明为什么bob 可以递归找到自己吗?
    • @barrey 你的意思是描述什么是 ADL?或者它如何改变模板函数查找规则?哦,我犯了一个错误,秒。好的,我认为已修复。我应该测试并添加一个示例,但帮助举办一个 2 岁的生日派对。 ;)
    • 它如何改变查找规则,以便函数模板bob 可以在 trailing-return-type 中找到。我实现了这个并且它有效,我只是不明白如何。
    • 如果你愿意,我可以在 code 中编辑。
    • @barry struct alice{}; 不应该是模板。在bob 中,将class Alice 添加到template&lt;&gt; args,如果现在未使用,可能删除T(抱歉在手机上,使用内存)。将 arg 更改为 Alice 而不是 alice&lt;T&gt;。只需alice{} 即可致电。它的工作原理是依赖于模板参数的 adl 查找(在 alice 上),该查找被推迟到实例化点,而不是声明模板的位置(无法找到自身)。这就是为什么alicebob 需要在同一个命名空间中:递归bob 是通过alice 上的ADL 找到的。
    【解决方案3】:

    我设法通过单独计算类型来做到这一点,但这似乎没有必要,尽管它很短。

    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...));
    }
    

    非常感谢更优雅的解决方案

    【讨论】:

    • 非特化的make_vector的实现可以简单地是return {first, make_vector&lt;T&gt;(sizes...)}
    【解决方案4】:

    当我发布这个时,我不知道其他答案。不要删除它,以防它对某人有用。当然,启用 C++14 的解决方案非常简单。

    使用[下面的代码](Demo@ideone可以实现:

      size_t n = 5;
      auto x = make_vector<int>(n+1, n*2, n*3);
    

    这是具有最少 cmets 的代码:

    // 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...);
    }
    

    【讨论】:

      【解决方案5】:

      最简单的解决方案是退回到 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 文件中,然后将其与我的项目的其余部分链接在一起。

      【讨论】:

      • @Barry 不,你不能。 Afaik,即使是 C++17 的 VLA 提案也只允许声明为局部变量的数组只有外部维度灵活。仍然无法处理类型中的可变大小,例如在threeDArray = (int (*)[2*n][3*n])malloc(...); 中。如果你的编译器有更多的 VLA 支持,那是因为它是一个编译器扩展,而不是因为它是 C++。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-17
      • 2011-03-27
      • 2019-08-01
      • 2012-08-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-27
      相关资源
      最近更新 更多