【问题标题】:Template specification of any kind of array任何类型数组的模板规范
【发布时间】:2021-04-11 18:12:28
【问题描述】:

我正在尝试摆脱 void* + size 在同一容器中存储任意数组类型的方法。

目前看起来有点像这样:

#include <iostream>
#include <map>
#include <string>
#include <cstddef>

struct fat_pointer {
  void *data;
  size_t size;
  size_t count;

  fat_pointer() : data(nullptr), size(0), count(0)
  {
  }

  fat_pointer(void *data_, size_t size_, size_t count_) :
    data(data_), size(size_), count(count_)
  {
  }

  bool valid() const {
    return data != nullptr;
  }

  template <typename T>
  const T as() {
    return static_cast<T>(data);
  }
};

int main(int argc, char* argv[])
{
  // data can be anything, these two are just for example
  const double v1[] = {1.1, 2.2, 3.3, 4.4, 5.5};
  const int v2[] = {1, 2, 3, 4, 5};

  std::map<std::string, fat_pointer> data;

  data.insert(std::pair<std::string, fat_pointer>("V1", fat_pointer((void*)v1, sizeof(v1[0]), sizeof(v1) / sizeof(v1[0]))));
  data.insert(std::pair<std::string, fat_pointer>("V2", fat_pointer((void*)v2, sizeof(v2[0]), sizeof(v2) / sizeof(v2[0]))));

  auto values = data["V1"];

  if (values.valid()) {
    std::cout << values.as<double*>()[2] << std::endl;
  }

  return 0;
}

这种方法非常容易出错,不提供任何类型的验证,不允许对元素轻松计数或应用算法,所以我真的想摆脱它。

有没有办法告诉编译器该值将是一个任意类型的数组?或者有没有其他方法可以避免fat_pointer hack?

【问题讨论】:

  • 也许你正在寻找类似std::any的东西。
  • 根据std::any 的描述,我将不得不创建类似std::vector&lt;std::any&gt; 的东西,这绝对不是一个选项,因为它会使处理这些数据变得非常困难
  • std::any 可以保存一个(智能)指针,并且可以检查您的 fat_pointer 是否丢失。
  • 我正在使用来自 3rd 方库的数组,它们不应该是我的代码中的 delete[]。最重要的是std::any 不会做,因为我在很多地方检查count。我想使用std::any_of 之类的东西,但是那里的类型太多+其他代码可能需要添加更多:(
  • 如果你想要像std::map 这样的容器来保存多个不同类型的数组,那么如果你不想使用std::anystd::variant 作为数组元素类型,你基本上会被困住使用void*。容器元素必须是相同的类型,就像数组元素一样。

标签: c++ types casting type-conversion


【解决方案1】:

如果您事先知道数组的类型,则可以使用std::variant。您可以更改fat_pointer 结构以保存std::pair&lt;T*, T*&gt; 类型的变体,其中firstsecond 成员将分别保存指向数组开头和结尾的指针。然后有一个模板化的构造函数来捕获数组的类型。

std::variant 会给你你想要的类型安全。另外,现在您存储了开始和结束指针,您可以将它们与标准算法一起使用。

如果您要以统一的方式处理所有数组,则不需要std::visit 中的单独访问者函数。你可以在std::pair&lt;T*, T*&gt; 中输入T 使用:

using T = std::decay_t<decltype(*arg.first)>;

然后使用T

这是一个例子:

#include <iostream>
#include <iomanip>
#include <variant>
#include <vector>
#include <iterator>
#include <algorithm>

using PairVariant = std::variant<std::pair<const int*, const int*>, std::pair<const double*, const double*>>;

struct fat_pointer {
  PairVariant mPtrs;
  
  template<typename T>
  fat_pointer(T* begin, T* end): mPtrs{std::pair<T*, T*>(begin, end)} {}

};

int main()
{
    const double v1[] = {1.1, 2.2, 3.3, 4.4, 5.5};
    const int v2[] = {1, 2, 3, 4, 5};

    fat_pointer ptr1{v1, std::end(v1)};
    fat_pointer ptr2{v2, std::end(v2)};

    std::vector<fat_pointer> vec{ptr1, ptr2};

    for (auto& v: vec) {
        std::visit([] (auto&& arg) {
            using T = std::decay_t<decltype(*arg.first)>;  //type of T in std::pair<T*, T*>
            std::copy (arg.first, arg.second, std::ostream_iterator<T>(std::cout, " "));
        }, v.mPtrs);

        std::cout << std::endl;
    }
    
    return 0;
}

输出:

1.1 2.2 3.3 4.4 5.5 
1 2 3 4 5 

直播demo.


如果您事先不知道要存储的数组的类型,那么我认为您将不得不使用std::any。您可以存储指向数组及其大小的指针,并有一个模板成员函数来执行std::any_cast。但是,如果有许多不同类型的数组,您最终会在运行时针对这些类型进行测试,这可能会非常难看!

struct fat_pointer {
    std::any mPtr;
    std::size_t mSize;

    template<typename T>
    fat_pointer(T* begin, T* end): mPtr{begin}, mSize(end-begin) {}

    template<typename T>
    T AnyCast()
    {
        if (T* ptr = std::any_cast<T>(&mPtr)) {
            return *ptr;
        }
        return nullptr;
    }
};

这是example

【讨论】:

  • 我不知道所有可能的类型,这就是诀窍。
  • @Daniel 但是你没有一组有限的数组类型吗?您可以随意扩展我的答案中的std::variant 类型,但编译器需要知道您拥有的数组类型。
  • …除非程序的设计允许在其他翻译单元中声明这些类型。
  • @spectras 如果在编译时类型未知,那么我看不到使用 std::any 的替代方法。正如 OP 在他的 cmets 中所说,这可能会导致非常丑陋的类型检查。我已经更新了我的答案来举个例子。在我看来,std::variant 更好。
  • 确实如此。不相关,但我很惊讶你的回答还没有吸引任何投票。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-25
  • 1970-01-01
  • 2015-06-17
  • 1970-01-01
  • 2010-12-22
  • 2018-12-05
相关资源
最近更新 更多