Differentiate between 1D and 2D container in template class constructor (SFINAE)

时间:2015-10-30 23:21:20

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

So, I have a class, which has an array of arrays as a private member. I wish to have two constructors for each case (1D or 2D). But of course their declaration happens to be the same, so template deduction can't do its job without me doing something about it. Here's the code:

Edit: I also need it to work with STL containers like vector or C++ array. That is why I am overcomplicating and not going with the "arrays" fix.

#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();
}

Note: I've been reading about enable_if, but I don't quite understand how it works. Can it be done with that?

2 个答案:

答案 0 :(得分:2)

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

此处不需要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保留以&#34; _t&#34;结尾的类型名称,因此在您自己的代码中避免使用它们通常是个好主意。 (令人讨厌,我知道。)标准C ++将使用形式为RowsType等的Camel Case,然后为该类用户使用 typedef a rows_type

但请注意,rows_t实际上并不是一种类型 - 它是值。更好的名称就像NRows

希望这有帮助。

答案 1 :(得分:1)

First, you have to "teach" the compiler what's 2D and what's not. Hence, you have to define something like the following type trait: 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 {}; Then you could set up your class definition in the following way: 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