I wonder why this error comes when I try to compile my code:
C:\Users\<UserName>\AppData\Local\Temp\ccaqeCwE.ltrans0.ltrans.o: In function `setup':
C:\Users\<UserName>\Documents\Arduino\SillyMathexample/SillyMathexample.ino:6: undefined reference to `SillyMath<float, int>::add(float, int)'
collect2.exe: error: ld returned 1 exit status
exit status 1
Error compiling for board Arduino Uno.
The program is here :
#include <SillyMath.h>
SillyMath<float, int> silly;
void setup() {
Serial.begin(9600);
Serial.print(silly.add(2.5, 5));
}
void loop() {
// put your main code here, to run repeatedly:
}
The .h file of SillyMath library :
#ifndef SillyMath_h
#define SillyMath_h
#include "Arduino.h"
template <typename A, typename B>
class SillyMath
{
public:
A add(A number_one, B number_two);
};
#endif
The .cpp file :
#include "Arduino.h"
#include "SillyMath.h"
template <typename A , typename B>
A SillyMath<A, B>::add(A number_one, B number_two){
A result;
result = number_one + number_two;
return(result);
}
Fully specialized. Template specialization allows you to gives functions and classes special behaviors for certain template parameter. Partial specialization is when you specify some of the template parameters, and full specialization is when you specify all of them.
Since you are not using any template specialization, the function definition must be in the header file like this:
#ifndef SillyMath_h
#define SillyMath_h
#include "Arduino.h"
template <typename A, typename B>
class SillyMath
{
public:
A add(A number_one, B number_two);
};
template <typename A , typename B>
A SillyMath<A, B>::add(A number_one, B number_two){
A result;
result = number_one + number_two;
return(result);
}
#endif
So if I am to use template specialisation, how should it be done ? (Sorry for my love for British English ... I mean, I wrote 'specialisation' instead of 'specialization')
Does it require me to change things in the .cpp file ?
Well, from an example I checked on Geeks for Geeks, I see that template specialisation helps in producing a particular output for the specified data type. Does that mean that I needn't delete the.cpp file, but define the same function specifically for the required types in the header file ?
Like :
#ifndef SillyMath_h
#define SillyMath_h
#include "Arduino.h"
template <typename A, typename B>
class SillyMath
{
public:
A add(A number_one, B number_two);
};
template <>
float SillyMath<float, int>::add(float number_one, int number_two){
float result;
result = number_one + number_two;
return(result);
}
#endif
The entire point of template functions is that you don't have to specialize them unless you need to provide special behaviors for certain types. And you usually shouldn't specialize them unless you have a specific reason to. Just use the file as I gave it to you.
What you have done is how you would write a fully specialized function, but there are some problems with it.
First, because it's fully specialized, it would have to be in the cpp file. In the header like that you could get "multiple definition" compilation errors if you #include the header in multiple files.
Second, you are only providing a specialized definition, so you can only use the function with <float,int> types. You have no generic definition to be used for other type combinations.
Here's an example from my own libraries of why you would specialize a template function. My EMEMRef let's you create an object that can read and write to the EEPROM just like a normal variable with the = operator. That is done through the to_value() and set_value(T) functions. The unspecialized function uses the eeprom_read_block and eeprom_update_block functions that will work on any data type, and I provide specializations for the single-byte types to specifically use the single-byte eeprom functions as an optimization.
Snippet from EMEM.h
// EMEMRef semantics are intended to make EEPROM variables usable like
// normal variables in code.
template<class T>
struct EMEMRef
{
public:
typedef T value_type;
typedef EMEMPtr<T> pointer_type;
private:
typedef EMEMRef<T> self_type;
public:
constexpr EMEMRef( T * const ptr )
: _ptr(ptr) {}
value_type to_value() const;
self_type& set_value(const value_type& val);
operator value_type() const {return to_value();}
self_type& operator=(const value_type& val) {return set_value(val);}
protected:
T * const _ptr;
};
// to_value function
// single byte specializations in .cpp file.
template<class T>
typename EMEMRef<T>::value_type EMEMRef<T>::to_value() const
{
T local = T();
eeprom_read_block( (void*)&local, _ptr, sizeof(T) );
return local;
}
// set_value function
// single byte specializations in .cpp file.
template<class T>
EMEMRef<T>& EMEMRef<T>::set_value( const typename EMEMRef<T>::value_type& val )
{
eeprom_update_block( &val, _ptr, sizeof(val) );
return *this;
}
Probably I must not have understood well (since I am a beginner at C/C++), so I may get to understand things once I work it all out; but thanks anyway, because you were inclined to help