Error when compiling sketch with .cpp and .h file: undefined reference

Hallo,
I have developed a simple c++ template with intention to use it in my further projects. I have placed the .cpp, .h and .ino files into one program folder. The program folder is located in Arduino folder. When I try to compile it under Arduino IDE, I obtain undefined reference errors. Can you help me to fix this issue? Please see the details below.

The content of the program folder is:

\test_dataset.ino
\dataset.h
\dataset.cpp

The program folder is located just below the Arduino folder:

Arduino\test_dataset

The content of the the files is:

test_dataset.ino:

#include "dataset.h"

void setup() {
  // put your setup code here, to run once:
  Dataset<Dataset<int>> daty;

  int j;
  int k;

  Serial.begin(9600);

  daty.nastavVelikost(3);
  
  daty[0].nastavVelikost(2);
  daty[1].nastavVelikost(2);
  daty[2].nastavVelikost(2);

  for(j=0;j<3;++j){
    for(k=0;k<2;++k){
      daty[j][k] = 2*j + k;
      
      Serial.print("daty[");
      Serial.print(j);
      Serial.print("]");
      Serial.print("[");
      Serial.print(k);
      Serial.print("] = ");
      Serial.println(daty[j][k]);
     
    }  
  }

}

void loop() {
  // put your main code here, to run repeatedly:

}

dataset.h:

#ifndef _DATASET_

#define _DATASET_

#include <Arduino.h>

template <class T>
class Dataset {
  private:
  // pocet prvku pole  
  unsigned int N;    
  public:
  
  Dataset();
  Dataset( unsigned int i_N);
  ~Dataset();
  
  T * pole;
 
  // pocet nenulovych prvku pole - volitelna promenna,
  // ktera muze byt dale pouzita ve vypoctech napr. FFT
  unsigned int Nfyz;
  
  // zapisovani prvku Datasetu a cteni z nich
  T & operator[](unsigned int i_n);
  
  // prepisovani pole jinym polem
  Dataset<T> & operator=(Dataset & i_Dts);
  
  //nastavovani velikosti pole vcetne kopie starych prvku
  int nastavVelikost(unsigned int i_N);
  unsigned int velikost();
  
  T minVal();
  T maxVal();
};

#endif

dataset.cpp:

#include "dataset.h"

template <class T>
Dataset<T>::Dataset(){
  pole = NULL;
  N = 0;
  Nfyz = 0;
}

template <class T>
Dataset<T>::Dataset(unsigned int i_N){
  
  if(i_N > 0){
    pole = new T [i_N];
    memset( pole, 0, i_N*sizeof(T));
    
    N = i_N;
    Nfyz = 0;
  }
  else{
    pole = NULL;
    
    N = 0;
    Nfyz = 0;
    Serial.println("Dataset: v konstruktoru datasetu je zaporny argument");  
  }
}


template <class T>
Dataset<T>::~Dataset(){
  if(pole != NULL){
    delete [] pole;    
  }
}

template <class T>
T & Dataset<T>::operator[](unsigned int i_n){
  bool horniMez;

  horniMez = (i_n < N);
  
  if(pole != NULL && horniMez){
    return pole[i_n]; 
  }
  else{
    Serial.println("Dataset:operator[] index je mimo rozsah pole");
  }
}

template <class T>
Dataset<T> & Dataset<T>::operator=(Dataset<T> & i_Dts){
  int k;
  int velikostPole;
  bool vytvorPole = false;
  
  velikostPole = i_Dts.N;

  // pole je jiz vytvorene
  if(pole != NULL){
    
    //vstupni pole nema stejnou velikost
    if(N != velikostPole){
      delete [] pole;
      pole = NULL;
      
      vytvorPole = true;
    }

    // vstupni pole je nenulove
    if(velikostPole > 0){
    // pole nemaji stejnou velikost
      if(vytvorPole){
        N = velikostPole;
        pole = new T [velikostPole];
      }
      
      memcpy( pole, i_Dts.pole, N*sizeof(T));
      Nfyz = i_Dts.Nfyz;
    }
    // vstupni pole je nulove
    else{
      N = 0;
      Nfyz = 0;
    }
  }
  //pole jeste neni vytvorene 
  else{
    if(velikostPole > 0){
      pole = new T [velikostPole];
      N = velikostPole;
      memcpy( pole, i_Dts.pole, N*sizeof(T));
      
      Nfyz = i_Dts.Nfyz;
    }
    // vstupni pole je nulove
    else{
      //pole = NULL;
      N = 0;
      Nfyz = 0;
    }
   }

   return *this;
}

template <class T>
int Dataset<T>::nastavVelikost(unsigned int i_N){
 bool zkopirujStaraData = false; 
 T * buff = new T [N];

 if(i_N == 0){
    if(pole != NULL){
      delete [] pole;
      
      pole = NULL;
      N = 0;
      Nfyz = 0;
    }
    else{
      N = 0;
      Nfyz = 0;
    }

    return 0;
  }
  else{
    if(pole != NULL){
        memcpy( buff, pole, N*sizeof(T));
        zkopirujStaraData = true;
        
        delete [] pole;
    }
    
    pole = new T [i_N];
    
    memset( pole, 0, i_N*sizeof(T));
    
    if(zkopirujStaraData){
        memcpy( pole, buff, min(i_N,N)*sizeof(T));
    }
  }
  
  N = i_N;
  
  delete [] buff;
}

template <class T>
T Dataset<T>::minVal (){
    int k;
    T minVal;
    
    if(N == 0){
        return T(0);
    }
    
    minVal = (*this)[0];
    for(k=0;k<N;++k){
        if((*this)[k] < minVal){
            minVal = (*this)[k];
        }
    }
    
    return minVal;
}

template <class T>
T Dataset<T>::maxVal (){
    int k;
    T maxVal;
    
    if(N == 0){
        return T(0);
    }
    
    maxVal = (*this)[0];
    for(k=0;k<N;++k){
        if((*this)[k] > maxVal){
            maxVal = (*this)[k];
        }
    }
    
    return maxVal;
}

template <class T>
unsigned int Dataset<T>::velikost(){
    return N;
}

The Error message, that I obain after compilation in Arduino IDE:

Arduino: 1.8.8 (Linux), Board: "ESP32 Dev Module, Disabled, Huge APP (3MB No OTA/1MB SPIFFS), 240MHz (WiFi/BT), QIO, 80MHz, 4MB (32Mb), 921600, None"

sketch/test_dataset.ino.cpp.o:(.literal._Z5setupv+0x20): undefined reference to `Dataset<Dataset<int> >::Dataset()'
sketch/test_dataset.ino.cpp.o:(.literal._Z5setupv+0x24): undefined reference to `Dataset<Dataset<int> >::nastavVelikost(unsigned int)'
sketch/test_dataset.ino.cpp.o:(.literal._Z5setupv+0x28): undefined reference to `Dataset<Dataset<int> >::operator[](unsigned int)'
sketch/test_dataset.ino.cpp.o:(.literal._Z5setupv+0x2c): undefined reference to `Dataset<int>::nastavVelikost(unsigned int)'
sketch/test_dataset.ino.cpp.o:(.literal._Z5setupv+0x30): undefined reference to `Dataset<int>::operator[](unsigned int)'
sketch/test_dataset.ino.cpp.o:(.literal._Z5setupv+0x34): undefined reference to `Dataset<Dataset<int> >::~Dataset()'
sketch/test_dataset.ino.cpp.o: In function `setup()':
/home/dalibor/Arduino/test_dataset/test_dataset.ino:5: undefined reference to `Dataset<Dataset<int> >::Dataset()'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:12: undefined reference to `Dataset<Dataset<int> >::nastavVelikost(unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:14: undefined reference to `Dataset<Dataset<int> >::operator[](unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:14: undefined reference to `Dataset<int>::nastavVelikost(unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:15: undefined reference to `Dataset<Dataset<int> >::operator[](unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:15: undefined reference to `Dataset<int>::nastavVelikost(unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:16: undefined reference to `Dataset<Dataset<int> >::operator[](unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:16: undefined reference to `Dataset<int>::nastavVelikost(unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:20: undefined reference to `Dataset<Dataset<int> >::operator[](unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:20: undefined reference to `Dataset<int>::operator[](unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:28: undefined reference to `Dataset<Dataset<int> >::operator[](unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:28: undefined reference to `Dataset<int>::operator[](unsigned int)'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:5: undefined reference to `Dataset<Dataset<int> >::~Dataset()'
/home/dalibor/Arduino/test_dataset/test_dataset.ino:5: undefined reference to `Dataset<Dataset<int> >::~Dataset()'
collect2: error: ld returned 1 exit status
exit status 1
Error compiling for board ESP32 Dev Module.
Invalid library found in /home/dalibor/Arduino/libraries/TFT_eSPI_user_SETUPS: no headers files (.h) found in /home/dalibor/Arduino/libraries/TFT_eSPI_user_SETUPS

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

Can anybody give an advice how to fix the "undefined reference" errors?

Thanks.

First of all, creating your own containers is often a bad idea, especially if you have access to the STL, which is the case on an ESP32. If you do create your own containers, use a class that is solely responsible for dynamic memory management, without anything else.
Your Dataset class also violates rule of three, so it might leak.
Dynamic arrays have been used for decades, reinventing the wheel and implementing your own version is rarely a good idea, and you'll often end up with a buggy container. Use the well-tested standard library containers instead.

It looks like you could replace most of the Dataset with [

std::vector

](std::vector - cppreference.com) and [

std::max_element

](std::max_element - cppreference.com)/std:::min_element.

As a side note, don't use memset and memcpy to copy or initialize arrays. To zero-initialize a dynamically allocated array, simply add parentheses after your call to new. To initialize an array to a value, use std::fill, and to copy elements of an array, use std::copy.

That being said, you're getting a linker error because you tried defining function templates in their own translation unit. Function templates are not functions. They are templates that can be used to create actual functions. When you use a function template, the compiler performs template argument deduction and substitution, it creates a concrete function instantiation out of an abstract template.
In your case, it substitutes the template parameter "T" by "int". To perform this substitution, the compiler needs access to the full definition of the function template, because it actually takes your code, replaces all "T"s by "int"s and compiles it.
You cannot compile and link them separately, because the compiler doesn't know beforehand what instantiations of the template will be necessary. Will the program need Dataset, or Dataset, or even Dataset<Dataset>? There's an infinite amount of possibilities.

In short: to use a function template, you need its definition, so include the definition in the header file with the declaration, not in a separate .CPP source file.

Pieter

Hi PieterP,
thank you for recommending me the vector class and pointing out its particular member functions suitable for my purposes. I understand, that it is a good practice to use containers developed and tested by professionals.

If you do create your own containers, use a class that is solely responsible for dynamic memory management, without anything else.

At this point I just want to understand what you exactly mean in my case. Do you mean crating field of the dataset template and its deleting on the heap? If so, I tried to handle that in the constructor and the destructor.

Your Dataset class also violates rule of three, so it might leak.

Thank you for pointing that. So in my case, the template is missing the copy assignment constructor, right?

As a side note, don't use memset and memcpy to copy or initialize arrays. To zero-initialize a dynamically allocated array, simply add parentheses after your call to new

Then what function or code do you recommend for field copying. I do not see anything bad on memcpy() on first sight. Can you explain it please? I agree that make field of zeros (of any type) through new operator is best done with constructor zeroing its elements.

You cannot compile and link them separately, because the compiler doesn't know beforehand what instantiations of the template will be necessary. Will the program need Dataset, or Dataset, or even Dataset<Dataset>? There's an infinite amount of possibilities.

Well, that is the problem. To be sure I understand it correctly - The template declaration and definition has to be in the same translation unit as a code, which uses concrete instantiation of the template.

Thank you,
Dalibor

dee25:
1.

At this point I just want to understand what you exactly mean in my case. Do you mean crating field of the dataset template and its deleting on the heap? If so, I tried to handle that in the constructor and the destructor.

Dynamically allocating and deallocating memory has nothing to do with Datasets, operations like finding the maximum element in a dataset are independent of how the memory of the dataset was allocated.
Specifically, I would create a storage class that handles the dynamic allocations, has a constructor and a destructor, copy constructor and assignment, move constructor and assignment, and maybe a resize method, element access and iterators. Its only purpose is to manage dynamically allocated arrays.

You can then include such a dynamically allocated array as a member (field) in your Dataset class. This allows the Dataset class to focus on dataset-specific tasks only.
Since the dynamic array storage class already defines the necessary destructors, move assignment operators, copy assignment, etc., you don't need to define any of those in the Dataset class.

In 99.9% of cases, std::vector is a good fit for managing dynamically allocated arrays.

dee25:
2.

Thank you for pointing that. So in my case, the template is missing the copy assignment constructor, right?

Both. You need a copy assignment operator and a copy constructor. (You can often call one from the other, so you don't have to implement it twice.)
In your case, you already have something that resembles a copy constructor, but the signature is incorrect, it should be Dataset &operator=([b]const[/b] Dataset &other).
You also want a move constructor and assignment, since copying a large array is expensive. Move semantics allow you to reuse the storage of the container you're moving from. This is important for limiting the peak memory usage of your program as well, which is essential on embedded systems with limited RAM.

Again, std::vector provides all of those, so I'd use that in any serious project.
OTOH, trying to implement the same functionality yourself might be a good learning experience, but I wouldn't use it for critical programs.

dee25:
3.

Then what function or code do you recommend for field copying. I do not see anything bad on memcpy() on first sight. Can you explain it please? I agree that make field of zeros (of any type) through new operator is best done with constructor zeroing its elements.

The problem with memcpy is that it copies memory, not objects. This is fine in C, where it originated, but often leads to undefined behavior in C++, where the construction and destruction of objects are deterministic and carefully defined by the Standard.

In your example, you are default-initializing an array of T using pole = new T [velikostPole]. Operator new explicitly calls the default constructor for each element in the array.
Then you're memcpy'ing the other array's bit patterns to the new array, without calling the correct copy constructors for T and its members. Pointers to other memory are simply copied, leading to all kinds of problems, especially if they are owning pointers that point to storage allocated on the heap, as that'll most definitely lead to a double free of the memory.
The correct solution is to copy the objects (i.e. by calling the appropriate copy constructor), not the raw memory bit patterns. That's why you should always use std::copy instead of memcpy (unless you explicitly want to copy the bit pattern of course, which is rare, and in C++20, you should use std::bit_cast, in previous versions, you're stuck with memcpy).

A similar argument can be made for std::fill, you don't want to use memset to set every bit of the memory of an object to zero, for instance. Especially since you don't know what's inside of an object (private fields, run-time type information, storage to make virtual function calls possible, etc.), in general, the Standard doesn't make any guarantees regarding the bit patterns of objects.

dee25:
4.

Well, that is the problem. To be sure I understand it correctly - The template declaration and definition has to be in the same translation unit as a code, which uses concrete instantiation of the template.

Correct.
If you don't want the implementations in the same header file, you can move them to a .TPP or .IPP file (for template or inline), and #include that file at the bottom of the header file.

Thank you for all the explanations, insights and examples PieterP. It seems that I am going to use std::vector class in my further projects. Anyway, the discussion with you about details of the Dataset template has been very beneficial for me.

Thanks again,
Dalibor

I tried to change the Dataset template according to your suggestions PieterP. I hope the rule of three is now obeyed :-). But when I tried to run a simple sketch I obtained an error. This happend since instantions of the Dataset template have not been properly initialized. I managed to track down the origin of the problem, but I do not understand why this happens. Can you give me a hint please?

Here is the decalaration and definition of Dataset template:

#ifndef _DATASET_

#define _DATASET_

#include <Arduino.h>

template <class T>
class Dataset {
  private:
  // pocet prvku pole  
  unsigned int N;    
  public:
  
  Dataset();
  Dataset( unsigned int i_N);
  Dataset( const Dataset<T> & Dts);
  ~Dataset();
  
  T * pole;
 
  // pocet nenulovych prvku pole - volitelna promenna,
  // ktera muze byt dale pouzita ve vypoctech napr. FFT
  unsigned int Nfyz;
  
  // zapisovani prvku Datasetu a cteni z nich
  T & operator[](unsigned int i_n);
  T operator[](unsigned int i_n) const;
  
  // prepisovani pole jinym polem
  Dataset<T> & operator=(const Dataset<T> & i_Dts);
  
  //nastavovani velikosti pole vcetne kopie starych prvku
  int nastavVelikost(unsigned int i_N);
  unsigned int velikost();
  
  T minVal();
  T maxVal();
};


template <class T>
Dataset<T>::Dataset(){
  pole = NULL;
  N = 0;
  Nfyz = 0;
}

template <class T>
Dataset<T>::Dataset(unsigned int i_N){
  
  if(i_N > 0){
    pole = new T [i_N];
        
    N = i_N;
    Nfyz = 0;
  }
  else{
    pole = NULL;
    
    N = 0;
    Nfyz = 0;
    Serial.println("Dataset: v konstruktoru datasetu je zaporny argument");  
  }
}


template <class T>
Dataset<T>::~Dataset(){
  if(pole != NULL){
    delete [] pole;    
  }
}

template <class T>
Dataset<T>::Dataset(const Dataset<T> & in){
    *this = in;
}

template <class T>
T & Dataset<T>::operator[](unsigned int i_n){
  bool horniMez;

  horniMez = (i_n < N);
  
  if(pole != NULL && horniMez){
    return pole[i_n]; 
  }
  else{
    Serial.println("Dataset:operator[] index je mimo rozsah pole");
  }
}

template <class T>
T Dataset<T>::operator[](unsigned int i_n) const {
  bool horniMez;

  horniMez = (i_n < N);
  
  if(pole != NULL && horniMez){
    return pole[i_n]; 
  }
  else{
    Serial.println("Dataset:operator[] index je mimo rozsah pole");
  }
}

template <class T>
Dataset<T> & Dataset<T>::operator=(const Dataset<T> & i_Dts){
  int k;
  int velikostPole;
  bool vytvorPole = false;
  
  char buffer [30];
  
  velikostPole = i_Dts.N;

  Serial.print("pole = ");
  sprintf(buffer, "with %%p:  x    = %p\n", pole);
  Serial.print(buffer);
  
  Serial.print("N = ");
  Serial.println(N);
  
  Serial.print("velikostPole = ");
  Serial.println(velikostPole);
  // pole je jiz vytvorene
  if(pole != NULL){
    
    //vstupni pole nema stejnou velikost
    if(N != velikostPole){
      Serial.println("Operator del= ");
      delete [] pole;
      pole = NULL;
      
      vytvorPole = true;
    }

    // vstupni pole je nenulove
    if(velikostPole > 0){
    // pole nemaji stejnou velikost
      if(vytvorPole){
                  
        N = velikostPole;
        pole = new T [velikostPole];
      }
      
      // kopirovani vstupniho pole
      for(k=0;k<N;++k){
                            
        (*this)[k] = i_Dts[k];  
        }
      
      Nfyz = i_Dts.Nfyz;
    }
    // vstupni pole je nulove
    else{
      N = 0;
      Nfyz = 0;
    }
  }
  //pole jeste neni vytvorene 
  else{
    if(velikostPole > 0){
      pole = new T [velikostPole];
      N = velikostPole;
      Serial.println("Operator null= ");
      Serial.print("N = ");
      Serial.println(N);
      
      Serial.print("pole = ");
        sprintf(buffer, "with %%p:  x    = %p\n", pole);
        Serial.print(buffer);
      
      //kopirovani vstupniho pole
      for(k=0;k<N;++k){
        (*this)[k] = i_Dts[k];  
        }
      
      Nfyz = i_Dts.Nfyz;
    }
    // vstupni pole je nulove
    else{
      //pole = NULL;
      N = 0;
      Nfyz = 0;
    }
   }

   return *this;
}

template <class T>
int Dataset<T>::nastavVelikost(unsigned int i_N){
 bool zkopirujStaraData = false; 
 int k;
 int M;
 
 T * buff = new T [N];

 if(i_N == 0){
    if(pole != NULL){
      delete [] pole;
      
      pole = NULL;
      N = 0;
      Nfyz = 0;
    }
    else{
      N = 0;
      Nfyz = 0;
    }

    return 0;
  }
  else{
    if(pole != NULL){
        for(k=0;k<N;++k){
            buff[k] = pole[k];
        }
        zkopirujStaraData = true;
        
        delete [] pole;
    }
    
    pole = new T [i_N];
    
    if(zkopirujStaraData){
        M = min(i_N,N);
        
        for(k=0;k<M;++k){
            pole[k] = buff[k];
        }
    }
  }
  
  N = i_N;
  
  delete [] buff;
}

template <class T>
T Dataset<T>::minVal (){
    int k;
    T minVal;
    
    if(N == 0){
        return T(0);
    }
    
    minVal = (*this)[0];
    for(k=0;k<N;++k){
        if((*this)[k] < minVal){
            minVal = (*this)[k];
        }
    }
    
    return minVal;
}

template <class T>
T Dataset<T>::maxVal (){
    int k;
    T maxVal;
    
    if(N == 0){
        return T(0);
    }
    
    maxVal = (*this)[0];
    for(k=0;k<N;++k){
        if((*this)[k] > maxVal){
            maxVal = (*this)[k];
        }
    }
    
    return maxVal;
}

template <class T>
unsigned int Dataset<T>::velikost(){
    return N;
}


#endif

Here is the sketch file

#include <dataset.h>

void setup() {
  // put your setup code here, to run once:
  Dataset<Dataset<int>> daty;
  Dataset<Dataset<int>> daty2;
  Dataset<int> * pDts;

  pDts = new Dataset<int> [3];
  
  int j;
  int k;

  char buffer [30];

  Serial.begin(9600);

  daty.nastavVelikost(3);
  
  daty[0].nastavVelikost(2);
  daty[1].nastavVelikost(2);
  daty[2].nastavVelikost(2);

  for(j=0;j<3;++j){
    for(k=0;k<2;++k){
      daty[j][k] = 2*j + k;
      
      Serial.print("daty[");
      Serial.print(j);
      Serial.print("]");
      Serial.print("[");
      Serial.print(k);
      Serial.print("] = ");
      Serial.println(daty[j][k]);
     
    }  
  }
  Serial.println();

  sprintf(buffer, "with %%p:  x    = %p\n", pDts[0].pole);
  Serial.print(buffer);
  
  
  daty2 = daty;

  for(j=0;j<3;++j){
    for(k=0;k<2;++k){
      daty[j][k] = 2*j + k;
      
      Serial.print("daty[");
      Serial.print(j);
      Serial.print("]");
      Serial.print("[");
      Serial.print(k);
      Serial.print("] = ");
      Serial.println(daty[j][k]);
     
    }  
  }

}

void loop() {
  // put your main code here, to run repeatedly:

}

The output from the console:

daty[0][0] = 0
daty[0][1] = 1
daty[1][0] = 2
daty[1][1] = 3
daty[2][0] = 4
daty[2][1] = 5

with %p:  x    = 0x0
pole = with %p:  x    = 0x0
N = 0
velikostPole = 3
Operator null= 
N = 3
pole = with %p:  x    = 0x3ffb84f8
pole = with %p:  x    = 0x3ffb1f10
N = 2148340460
velikostPole = 2
Operator del= 
CORRUPT HEAP: Bad head at 0x3ffb1f08. Expected 0xabba1234 got 0x3f000a38
assertion "head != NULL"

In my opinion, the problem is in creating of field of Dataset instantions. These instantions have been created as elements of a field in previous call of operator=() from object of class Dataset<Dataset>. The error occurs since the pointer pole (in the instantions Dataset) is not initialized to NULL as I expected (see output from console) - after allocation of the field. I do not know why this happens, since I think that when operator new [] is called, a field for required number of objects should be allocated and all objects should be initialized by default constructor.

I just want to understand, where this problem is comming from. If there are some other serious flaws in the declaration and definition of the template, I would welcome any suggestions too.

Thanks.

I didn't look at it in detail, but you forgot to initialize the members of Dataset in your copy constructor. Either use a delegating constructor, initialize the members explicitly, or provide defaults:

class Dataset {
  T *pole = nullptr;
  unsigned N = 0;
  // ...
};

Never use NULL in C++, it's an integer, not a pointer, and this can lead to unexpected results. Use nullptr instead.

Your resize function makes unnecessary copies. First allocate storage with the new size, copy the old elements over, deallocate the old storage, and then save the pointer to the new storage to the pole member.

Thanks for fast reply PieterP.

I didn't look at it in detail, but you forgot to initialize the members of Dataset in your copy constructor. Either use a delegating constructor, initialize the members explicitly, or provide defaults:

From your answer it seem that there are instantions of Dataset, which are initialized for the first time by the copy constructor. At which point of the code does this happen? From my point of view, all of the objects that are used should have been already initialized by a default constructor.

Never use NULL in C++, it's an integer, not a pointer, and this can lead to unexpected results. Use nullptr instead.

OK. Thanks.

Your resize function makes unnecessary copies. First allocate storage with the new size, copy the old elements over, deallocate the old storage, and then save the pointer to the new storage to the pole member.

That's true, thanks for pointing that out.

Well I know It was an elementary question, but it seems I have figured it out. The error is caused by line

(*this)[k] = i_Dts[k];

The rvalue is returned by value from operator const. When returning by value the new temporary object is initialized by copy constructor:

The problem lies in operator=() called in copy constructor, which assumes, that the object is already initialized.

dee25:
The rvalue is returned by value from operator const. When returning by value the new temporary object is initialized by copy constructor

I think the general convention is to return a const reference instead of a value. Copying values is expensive, and might throw.
std::vector also returns a const ref: std::vector<T,Allocator>::operator[] - cppreference.com

As a side note, you might also want to add a move constructor and a move assignment operator. This will reduce the number of allocations and copies you have to make, because the storage of temporaries can be reused.

I think the general convention is to return a const reference instead of a value. Copying values is expensive, and might throw.
std::vector also returns a const ref: std::vector<T,Allocator>::operator[] - cppreference.com

Ok, that is good idea.

As a side note, you might also want to add a move constructor and a move assignment operator. This will reduce the number of allocations and copies you have to make, because the storage of temporaries can be reused.

I have not used the move constructors before, but I will take a look on them.

Thanks PieterP!