【问题标题】:Differentiate between 1D and 2D container in template class constructor (SFINAE)在模板类构造函数(SFINAE)中区分一维和二维容器
【发布时间】:2015-10-30 23:21:20
【问题描述】:

所以,我有一个类,它有一个数组数组作为私有成员。我希望每种情况(一维或二维)都有两个构造函数。但当然,他们的声明恰好是相同的,所以如果我不做一些事情,模板推导就无法完成它的工作。代码如下:

编辑:我还需要它来处理向量或 C++ 数组等 STL 容器。这就是为什么我过于复杂并且不使用“数组”修复。

#include <iostream>
#include <array>

template<class T, std::size_t rows_t, std::size_t cols_t>
class test
{
private:
    std::array<std::array<T, cols_t>, rows_t> _data;
public:    
    auto begin() { return this->_data.begin(); }
    auto end() { return this->_data.end(); }


    //CONSTRUCTOR
    template<class type_t>
    test(const type_t &arr)
    {
        std::size_t j = 0;
        for (const auto &num : arr)
            this->_data[0][j++] = num;
    }

    template<class type_t>
    test(const type_t &arr)
    {
        std::size_t i = 0;
        for (const auto &el : arr)
        {
            std::size_t j = 0;
            for (const auto &num : el)
                this->_data[i][j++] = num;
            ++i;
        }
    }
};

int main()
{
    double arr[3] = { 1, 2, 3 };
    double arr2[2][2] = { {1, 2}, {3, 4} };

    test<double, 1, 3> obj = arr; 
    test<double, 2, 2> obj2 = arr2;

    for (const auto &i : obj2)
    {
        for (const auto &j : i)
            std::cout << j << " ";
        std::cout << std::endl;
    }

    std::cin.get();
}

注意:我一直在阅读有关 enable_if 的信息,但我不太了解它是如何工作的。能做到吗?

【问题讨论】:

  • 当您知道 _data 是二维的时,为什么还需要两个案例?
  • 为了方便。有时 _data 可能有 1 行和 3 列,使其成为一维的。
  • 您可以使用一维数组作为任何维度数组的后备数据结构并计算索引(至少在它们密集的情况下)。这将消除问题。但坦率地说,你已经有了一维数组(std::array),而二维数组应该是一个单独的数据类型(类)。
  • auto begin() { return this-&gt;_data.begin(); } 这将返回外部维度的迭代器。你确定这是你想要的吗?
  • 是的,我很确定。这就是我能够在示例末尾执行 ranged-for 循环的方式。

标签: c++ templates c++11 sfinae enable-if


【解决方案1】:

构造函数不应该相同,但您只提供了最通用的匹配。

这里不需要 SFINAE。只需为一维数组提供一个构造函数,为二维数组提供一个单独的构造函数:

template <typename T2, std::size_t N>
test( const T2 (&a)[N] )
{
  ...
}

template <typename T2, std::size_t M, std::size_t N>
test( const T2 (&a)[M][N] )
{
  ...
}

另一个注意事项:POSIX 保留以“_t”结尾的类型名,因此在您自己的代码中避免使用它们通常是个好主意。 (讨厌,我知道。)标准 C++ 将使用以下形式的 Camel Case:RowsType 等,然后为该类的用户使用 typedef rows_type

但是请注意,rows_t 实际上并不是一个类型——它是一个值。一个更好的名字应该是类似NRows

希望这会有所帮助。

【讨论】:

  • 请给出一些使用 CamelCase 代替蛇形大小写的标准 C++ 示例。
  • 认真的吗? TCharTTraitsForwardIterator 等。选择一个。
  • 对不起,我不是故意的。
【解决方案2】:

首先,您必须“教”编译器什么是 2D,什么不是。因此,您必须定义类似于以下类型特征的内容:

template<typename T>
struct is2D : public std::false_type {};
template<typename T, std::size_t N, std::size_t M>
struct is2D<std::array<std::array<T, M>, N>> : std::true_type {};
template<typename T>
struct is2D<std::vector<std::vector<T>>> : std::true_type {};
template<typename T, std::size_t N, std::size_t M>
struct is2D<T[N][M]> : std::true_type {};

然后您可以通过以下方式设置您的类定义:

template<class T, std::size_t rows_t, std::size_t cols_t>
class test{
  std::array<std::array<T, cols_t>, rows_t> _data;

  template<class type_t>
  std::enable_if_t<!is2D<type_t>::value, void>
  test_init(type_t const &arr) {
    std::size_t j = 0;
    for (const auto &num : arr) _data[0][j++] = num;
  }

  template<class type_t>
  std::enable_if_t<is2D<type_t>::value, void>
  test_init(type_t const &arr) {
    std::size_t i = 0;
    for(const auto &el : arr) {
      std::size_t j = 0;
      for (const auto &num : el) _data[i][j++] = num;
      ++i;
    }
  }

public:

  auto &operator[](const std::size_t &i) { return this->_data[i]; }
  auto begin() { return this->_data.begin(); }
  auto end() { return this->_data.end(); }

  //CONSTRUCTOR
  template<class type_t> test(type_t const &arr) { test_init(arr); }
};

LIVE DEMO

【讨论】:

  • 对不起,我已经这样做了。我应该指定的。但它并不能让我满意,因为它只适用于 C 数组,而不适用于向量或 C++ 数组。 :)
猜你喜欢
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多