Почему неявное преобразование типов в C ++ хорошо работает со структурами, а с классами - иначе - PullRequest
0 голосов

Я пытаюсь создать многомерный массив с инициализацией динамической памяти в C ++, но неявное преобразование типов работает одним способом, когда я работаю напрямую с функциями, и другим способом, когда я пытался создать оболочку класса вокруг этот указатель и именованные функции как члены класса.

Если шаблон типа T, например, int, со структурами предпочитает (T * &) тип над (T &) для указателя любой вложенности, но когда я сделал эти функции членами класса, они даже не могли неявно сделать преобразование T ** в (T * &).

Я понимаю, что T ***, например, не является T &, но функция пустого тела является нижней частью моей рекурсии, которая инициализирует n-мерный массив (указатель, что угодно). Это преобразование работает с функциями, не относящимися к классу, но не работает неявно с функциями-членами clas.

template<typename T, size_t N>
struct NDimensionalArray {
    typedef typename NDimensionalArray<T, N - 1>::type * type;
};

template<typename T>
struct NDimensionalArray<T, 1> {
    typedef T * type;
};

template<typename T>
void initializeNDimensionalArray(T &, size_t) { }

template<typename T>
void initializeNDimensionalArray(T *& arr, size_t n) {
    arr = new T[n];
    for (size_t i = 0; i < n; ++i)
        initializeNDimensionalArray(arr[i], n);
}

int main() {

    NDimensionalArray<int, 3>::type arr;
    initializeNDimensionalArray(arr, 2);

}

template <typename T, size_t N>
class NDimArray {
public:

        typename NDimensionalArray<T, N>::type arr{ nullptr };

        NDimArray<T, N>::NDimArray() {
           init(arr, 0); //exception, cannot transform T*** to T*&, 
                         //invokes T&                                               
        }

        void init(T &, size_t, int) { }

        void NDimArray<T, N>::init(T *& arr, size_t n) {

           arr = new T[n];

           for (size_t i = 0; i < n; ++i)
                init(arr[i], n);
        }

}

int main() {
    NDimArray<int, 3> arr(2);
}

Ответы [ 2 ]

2 голосов
/ 07 мая 2019

У вас нет неявного преобразования из int *** в int* & в вашем рабочем примере.

Давайте расширим типы, когда мы будем использовать ваш шаблон.

    NDimensionalArray<int, 3>::type arr;
    // a.k.a. 
    NDimensionalArray<int, 2>::type * arr;
    // a.k.a. 
    NDimensionalArray<int, 1>::type ** arr;
    // a.k.a. 
    int *** arr;

Поэтому, когда мы передаем его initializeNDimensionalArray, выводимый тип для T равен int **, который заменяет вас на

template<>
void initializeNDimensionalArray<int**>(int** *& arr, size_t n) {
    arr = new int**[n];
    for (size_t i = 0; i < n; ++i)
        initializeNDimensionalArray<int*>(arr[i], n);
}

, который создает шаблон сдругой аргумент, int * и т. д.

В случае, когда вы заключаете его в класс, ваш init получает определение T из того же единственного параметра шаблона, который вы передаете NDimensionalArray, поэтому он не совпадает, когда вы передаете ему int ***.Вам нужно init, чтобы быть участником.

template <typename T, size_t N>
class NDimArray {
public:
    typename NDimensionalArray<T, N>::type arr{ nullptr };

    NDimArray() {
       init(arr, 0);
    }
private:
    // T is the type we want here
    void init(T &, size_t) { }

    // U will be T, T *, T ** etc
    template<typename U>
    void init(U *& arr, size_t n) {
       arr = new U[n];
       for (size_t i = 0; i < n; ++i)
            init(arr[i], n);
    }
}
2 голосов
/ 06 мая 2019

Сначала я пытаюсь воспроизвести вашу ошибку при компиляции

Чтобы получить ошибку компиляции, сначала нужно сделать много изменений в коде, который вы дали, потому что он содержит несколько других ошибок, в следующий раз будьте осторожнее с нами; -)

Так, например, с:

#include <stddef.h> // size_t

template <typename T, size_t N>
struct NDimensionalArray {
    typedef typename NDimensionalArray<T, N - 1>::type * type;
};

template<typename T>
struct NDimensionalArray<T, 1> {
    typedef T * type;
};


template <typename T, size_t N>
class NDimArray {
public:

        typename NDimensionalArray<T, N>::type arr{ nullptr };

        NDimArray(int) {
           init(arr, 0); //exception, cannot transform T*** to T*&, 
                         //invokes T&                                               
        }

        void init(T &, size_t) { }

        void init(T *& arr, size_t n) {

           arr = new T[n];

           for (size_t i = 0; i < n; ++i)
                init(arr[i], n);
        }

};

int main() {
    NDimArray<int, 3> arr(2);
}

Компиляция:

pi@raspberrypi:/tmp $ g++ -pedantic -Wall -Wextra c.cc
c.cc: In instantiation of ‘NDimArray<T, N>::NDimArray(int) [with T = int; unsigned int N = 3u]’:
c.cc:38:28:   required from here
c.cc:21:17: error: invalid conversion from ‘NDimensionalArray<int, 3u>::type {aka int***}’ to ‘int’ [-fpermissive]
            init(arr, 0); //exception, cannot transform T*** to T*&,
                 ^~~
c.cc:25:14: note:   initializing argument 1 of ‘void NDimArray<T, N>::init(T&, size_t) [with T = int; unsigned int N = 3u; size_t = unsigned int]’
         void init(T &, size_t) { }
              ^~~~
c.cc:21:17: error: cannot bind rvalue ‘(int)((NDimArray<int, 3u>*)this)->NDimArray<int, 3u>::arr’ to ‘int&’
            init(arr, 0); //exception, cannot transform T*** to T*&,
                 ^~~
pi@raspberrypi:/tmp $ 

Чтобы устранить ошибку компиляции, просто измените строку, в которой она появляется, замените

    NDimArray(int) {
       init(arr, 0); //exception, cannot transform T*** to T*&, 
                     //invokes T&                                               
    }

от

    NDimArray(int) {
       init(**arr, 0);                                         
    }

потому что указатель и ссылка уже не одно и то же, поэтому нет никакого шанса с указателем на указатель, но ссылка может быть сделана из значения

Компиляция:

pi@raspberrypi:/tmp $ g++ -pedantic -Wall -Wextra c.cc
pi@raspberrypi:/tmp $ 

Выполнение завершается неудачно, потому что вы разыменовываете nullptr


Что вы хотите

Но код, который вы хотите (не пытаясь воспроизвести вашу ошибку), основанный на первом случае в вашем вопросе, на самом деле:

#include <stddef.h> // size_t

template<typename T, size_t N>
struct NDimensionalArray {
    typedef typename NDimensionalArray<T, N - 1>::type * type;
};

template<typename T>
struct NDimensionalArray<T, 1> {
    typedef T * type;
};

template<typename T>
void initializeNDimensionalArray(T &, size_t) { }

template<typename T>
void initializeNDimensionalArray(T *& arr, size_t n) {
    arr = new T[n];
    for (size_t i = 0; i < n; ++i)
        initializeNDimensionalArray(arr[i], n);
}

template<typename T, size_t N>
class NDimArray {
public:
    typename NDimensionalArray<T, N>::type arr;

    NDimArray(size_t n) {
      initializeNDimensionalArray(arr, n);
    }
};

int main()
{
  NDimArray<int, 3> nd(2);
}

Или, если вы предпочитаете вложенные определения:

#include <stddef.h> // size_t

template<typename TT, size_t NN>
class NDimArray {
  template<typename T, size_t N>
  struct NDimensionalArray {
    typedef typename NDimensionalArray<T, N - 1>::type * type;
   };

  template<typename T>
  struct NDimensionalArray<T, 1> {
    typedef T * type;
  };

  template<typename T>
  void initializeNDimensionalArray(T &, size_t) { }

  template<typename T>
  void initializeNDimensionalArray(T *& arr, size_t n) {
    arr = new T[n];
    for (size_t i = 0; i < n; ++i)
        initializeNDimensionalArray(arr[i], n);
  }

public:
    typename NDimensionalArray<TT, NN>::type arr;

    NDimArray(size_t n) {
      initializeNDimensionalArray(arr, n);
    }
};

int main()
{
  NDimArray<int, 3> nd(2);
}

Для них обоих нет ошибок / предупреждений во время компиляции:

pi@raspberrypi:/tmp $ g++ -g -pedantic -Wall -Wextra a.cc
pi@raspberrypi:/tmp $ 

и без проблем при исполнении

pi@raspberrypi:/tmp $ ./a.out
pi@raspberrypi:/tmp $ 

О вашем замечании

Если вы попытаетесь инициализировать созданный объект, вы получите исключение, поэтому он не создает его должным образом.

Предупреждение, чтобы не путать роли

  • параметр шаблона класса NDimArray указывает количество измерений (3 в NDimArray<int, 3> arr(2);)
  • параметр его конструктора указывает количество элементов в каждом измерении (2 в NDimArray<int, 3> arr(2);)

Инициализация / распределение правильные. Пример нового определения для main :

int main()
{
  const size_t dimsz = 2;
  NDimArray<int, 3> nd(dimsz);

  // initialize elements
  for (size_t i = 0; i != dimsz; ++i) 
    for (size_t j = 0; j != dimsz; ++j) 
      for (size_t k = 0; k != dimsz; ++k)
        nd.arr[i][j][k] = i*100+j*10+k;

  // check 
  for (size_t i = 0; i != dimsz; ++i)
    for (size_t j = 0; j != dimsz; ++j)
      for (size_t k = 0; k != dimsz; ++k)
        std::cout << "nd.arr[" << i << "][" << j << "][" << k << "] = " << nd.arr[i][j][k] << std::endl;

  return 0;
}

Компиляция и исполнение:

pi@raspberrypi:/tmp $ g++ -pedantic -Wall -Wextra a.cc
pi@raspberrypi:/tmp $ ./a.out
nd.arr[0][0][0] = 0
nd.arr[0][0][1] = 1
nd.arr[0][1][0] = 10
nd.arr[0][1][1] = 11
nd.arr[1][0][0] = 100
nd.arr[1][0][1] = 101
nd.arr[1][1][0] = 110
nd.arr[1][1][1] = 111
pi@raspberrypi:/tmp $ 

Версия без указателей

Конечно, также возможно не использовать указатели для реализации многомерного массива, например:

#include <iostream>

template<typename T, size_t N, size_t M>
struct NDimensionalArray {
  typedef typename NDimensionalArray<T, N - 1, M>::type type[M];
};

template<typename T, size_t M>
struct NDimensionalArray<T, 1, M> {
    typedef T type[M];
};

template<typename T, size_t N, size_t M>
class NDimArray {
public:
  typename NDimensionalArray<T, N, M>::type arr;

  NDimArray() {
    // nothing to do
  }
};

int main()
{
  const size_t dimsz = 2;
  NDimArray<int, 3, dimsz> nd;

  std::cout <<  "sizeof(nd) : " << sizeof(nd) << " (sizeof(int) : " << sizeof(int) << ")" << std::endl;

  // initialize array
  for (size_t i = 0; i != dimsz; ++i) 
    for (size_t j = 0; j != dimsz; ++j) 
      for (size_t k = 0; k != dimsz; ++k)
        nd.arr[i][j][k] = i*100+j*10+k;

  // check 
  for (size_t i = 0; i != dimsz; ++i)
    for (size_t j = 0; j != dimsz; ++j)
      for (size_t k = 0; k != dimsz; ++k)
        std::cout << "nd.arr[" << i << "][" << j << "][" << k << "] = " << nd.arr[i][j][k] << std::endl;

  return 0;
}

Компиляция и исполнение:

pi@raspberrypi:/tmp $ g++ -pedantic -Wall -Wextra mda.cc
pi@raspberrypi:/tmp $ ./a.out
sizeof(nd) : 32 (sizeof(int) : 4)
nd.arr[0][0][0] = 0
nd.arr[0][0][1] = 1
nd.arr[0][1][0] = 10
nd.arr[0][1][1] = 11
nd.arr[1][0][0] = 100
nd.arr[1][0][1] = 101
nd.arr[1][1][0] = 110
nd.arr[1][1][1] = 111
pi@raspberrypi:/tmp $ 
...