I've seem to be having problems with the linker while working on a slightly bigger project.
(I'm using Visual Studios 2019. I'm trying to recreate code from Lubos Briedas "Plasma Simulation by Example" and there are some mistakes in the book, even though most of it is fine a great introduction into simulations with C++.)
Currently I receive the following errors:
Output.obj : error LNK2019: unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Field_<double> &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Field_@N@@@Z) referenced in function "void __cdecl Output::fields(class World &,class std::vector<class Species,class std::allocator<class Species> > &)" (?fields@Output@@YAXAAVWorld@@AAV?$vector@VSpecies@@V?$allocator@VSpecies@@@std@@@std@@@Z)
Output.obj : error LNK2019: unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Field_<struct vec3<double> > &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Field_@U?$vec3@N@@@@@Z) referenced in function "void __cdecl Output::fields(class World &,class std::vector<class Species,class std::allocator<class Species> > &)" (?fields@Output@@YAXAAVWorld@@AAV?$vector@VSpecies@@V?$allocator@VSpecies@@@std@@@std@@@Z)
Species.obj : error LNK2019: unresolved external symbol "public: void __thiscall Field_<double>::scatter(struct vec3<double>,double)" (?scatter@?$Field_@N@@QAEXU?$vec3@N@@N@Z) referenced in function "public: void __thiscall Species::computeNumberDensity(void)" (?computeNumberDensity@Species@@QAEXXZ)
I've checked the spelling of the functions mentioned in the messages multiple times and also checked that there is no additional definition. I also looked up whether the operator<<
-overloading can be done outside of a class (like in the code) and it seems to be fine. Adding const
to the functions in the error messages don't solve them, so I don't think it has something to do with l/rvaulues. Most other solutions to this error I've found included adding something to the linker via the property pages, but since I don't include any special external library, I don't know what I would have to add there and if something needs to be added all.
Are there any other possibilities which might lead to this error? And how can I detect and solve what needs to be added or changed?
I've been stuck for quite some time now and hope one of you might be able to help me.
(If needed, I can provide the complete code, but I refrain from it for now, because I don't have a minimal working example and it might be to much without.)
Here is the code for the functions mentioned in the error messages:
// Output.h
#pragma once
#include <sstream>
#include <fstream>
#include <ostream>
#include <iostream>
#include "Fields_.h"
#include "World.h"
#include "Species.h"
namespace Output { void fields(World& world, std::vector<Species> &species); }
void Output::fields(World& world, std::vector<Species> &species);
// Output.cpp
#include "Output.h"
// write data to a file stream
template<typename T>
std::ostream& operator<<(std::ostream& out, Field_<T>& f) {
for (int k = 0; k < f.nk; k++, out << "
") // new line after each "k"
for (int j = 0; j < f.nj; j++)
for (int i = 0; i < f.ni; i++)
out << f.data[i][j][k] << " ";
return out;
}
// saves output in VTK format
void Output::fields(World& world, std::vector<Species>& species) {
std::stringstream name; // build file name
name << "fields.vti"; // here we just set it to a given string
// open output file
std::ofstream out(name.str());
if(!out.is_open()) { std::cerr << "Coulld not open " << name.str() << std::endl; return; }
// ImageData is a VTK format for structured Cartesian meshes
out << "<VTKFile type="ImageData">
";
double3 x0 = world.getX0();
double3 dh = world.getDh();
out << "<ImageData Origin="" << x0[0] << " " << x0[1] << " " << x0[2] << "" ";
out << "Spacing="" << dh[0] << " " << dh[1] << " " << dh[2] << "" ";
out << "WholeExtent="0 " << world.ni - 1 << " 0 " << world.nj - 1 << " 0 " << world.nk - 1 << "">
";
// output data stored on nodes (point data)
out << "<PointData>
";
// node volumes, scalar
out << "<DataArray Name="NodeVol" NumberOfComponents="1" format="ascii" type="Float64">
";
out << world.node_vol; // use the overloaded << operator
out << "</DataArray>
";
// potential, scalar
out << "<DataArray Name="phi" NumberOfComponents="1" format="ascii" type="Float64">
";
out << world.phi; // use the overloaded << operator
out << "</DataArray>
";
/* */ // output world.phi
// charge density, scalar
out << "<DataArray Name="rho" NumberOfComponents="1" format="ascii" type="Float64">
";
out << world.rho; // use the overloaded << operator
out << "</DataArray>
";
/* */ // output world.rho
// electric field, 3 component vector
out << "<DataArray Name="ef" NumberOfComponents="3" format="ascii" type="Float64">
";
out << world.ef; // uses overloaded << from Field_ and vec3
out << "</DataArray>
";
// close the tags
out << "</PointData>
";
out << "</ImageData>
";
out << "</VTKFile>
";
// species number densities
for (Species& sp : species) {
out << "<DataArray Name="nd." << sp.name << "" NumberOfComponents="1" format="ascii" type="Float64">
";
out << sp.den;
out << "</DataArray>
";
}
} // file closed here as 'out' goes out of scope
Moving the function with the error from the .cpp to the class in .h solved one error. But this isn't possible with the other errors, since there is to class to put them in.
// Fields_.h
#pragma once
#include <ostream>
//#include <utility>
#include "vec3.h"
template <typename T>
class Field_{
public:
// constructor
Field_(int ni, int nj, int nk) : ni{ ni }, nj{ nj }, nk{ nk }{
data = new T * *[ni]; // ni pointers to pointers of type T
for (int i = 0; i < ni; i++) {
data[i] = new T * [nj]; // allocte nj pointers to T
for (int j = 0; j < nj; j++)
data[i][j] = new T[nk]; // allocate nk objects of type T
}
// when creating a scalar Field (not Field_<double3>), initialization has to be done explicitly
if (!std::is_same<T, double3>::value) {
operator=(0);
}
//operator=(0); // call the overloaded operator= function
//(*this) = 0; // clear data (doesn't work)
}
// destructor, frees momory in reverse order
~Field_() {
if (data == nullptr) return; // return if unallocated
for (int i = 0; i < ni; i++) { // release memory in reverse order
for (int j = 0; j < nj; j++)
delete data[i][j];
delete data[i];
}
delete[] data;
data = nullptr; // mark as free
}
// data acces operator
T** operator[] (int i) { return data[i]; }
// overload the assignment operator
Field_<T>& operator= (const T s) {
for (int i = 0; i < ni; i++)
for (int j = 0; j < nj; j++)
for (int k = 0; k < nk; k++)
data[i][j][k] = s;
return *this; // return refernce to self
}
// copy constructor
Field_(const Field_& other) :
Field_{ other.ni,other.nj, other.nk } {
for (int i = 0; i < ni; i++)
for (int j = 0; j < nj; j++)
for (int k = 0; k < nk; k++)
data[i][j][k] = other(i, j, k);
}
// move construtor
Field_(Field_ &&other) noexcept:
ni{ other.ni }, nj{ other.nj }, nk{ other.nk } {
if (data) this->~Field_(); // deallocate own data /*doesn't work??? why is it needed?*/
data = other.data; // steal the data
other.data = nullptr; // invalidate
}
// move assignment operator
Field_& operator=(Field_&& f) {
if (data) ~Field_(); // deallocate own data
data = f.data; f.data = nullptr; return *this;
}
// read-only acces to data[i][j][k]
T operator() (int i, int j, int k) const { return data[i][j][k]; }
void operator /=(const Field_& other) {
for (int i = 0; i < ni; i++)
for (int j = 0; j < nj; j++)
for (int k = 0; k < nk; k++) {
if (other.data[i][j][k] != 0)
data[i][j][k] /= other(i, j, k); // in the book data[i][j][k] /= other[i][j][k];
else
data[i][j][k] = 0;
}
}
Field_& operator += (const Field_& other) {
for (int i = 0; i < ni; i++)
for (int j = 0; j < nj; j++)
for (int k = 0; k < nk; k++)
data[i][j][k] += other(i, j, k);
return (*this);
}
// compound multiplication
Field_& operator *= (double s) {
for (int i = 0; i < ni; i++)
for (int j = 0; j < nj; j++)
for (int k = 0; k < nk; k++)
data[i][j][k] *= s;
return (*this);
}
// multiplikation operator, returns new Field set to f*s
friend Field_<T> operator*(double s, const Field_<T>& f) {
Field_<T> r(f);
return std::move(r *= s); // force move
//return move(r *= s); // force move
//return r;
//return r *= s;
}
void scatter(double3 lc, double value) {
// make sure we are in domain
if (lc[0]<0 || lc[0]>ni - 1 || lc[1]<0 || lc[1]>nj - 1 || lc[2]<0 || lc[2]>nk - 1) return;
// compute the cell index and the fractional distances
int i = (int)lc[0];
double di = lc[0]