0

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 << "\n") // 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\">\n";
    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 << "\">\n";

    // output data stored on nodes (point data)
    out << "<PointData>\n";

    // node volumes, scalar
    out << "<DataArray Name=\"NodeVol\" NumberOfComponents=\"1\" format=\"ascii\" type=\"Float64\">\n";
    out << world.node_vol;  // use the overloaded << operator
    out << "</DataArray>\n";

    // potential, scalar
    out << "<DataArray Name=\"phi\" NumberOfComponents=\"1\" format=\"ascii\" type=\"Float64\">\n";
    out << world.phi;   // use the overloaded << operator
    out << "</DataArray>\n";
    /*  */  // output world.phi

    // charge density, scalar
    out << "<DataArray Name=\"rho\" NumberOfComponents=\"1\" format=\"ascii\" type=\"Float64\">\n";
    out << world.rho;   // use the overloaded << operator
    out << "</DataArray>\n";
    /*  */  // output world.rho

    // electric field, 3 component vector
    out << "<DataArray Name=\"ef\" NumberOfComponents=\"3\" format=\"ascii\" type=\"Float64\">\n";
    out << world.ef;    // uses overloaded << from Field_ and vec3
    out << "</DataArray>\n";

    // close the tags
    out << "</PointData>\n";
    out << "</ImageData>\n";
    out << "</VTKFile>\n";

    // species number densities
    for (Species& sp : species) {
        out << "<DataArray Name=\"nd." << sp.name << "\" NumberOfComponents=\"1\" format=\"ascii\" type=\"Float64\">\n";
        out << sp.den;
        out << "</DataArray>\n";
    }
}       // 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] - i;
        int j = (int)lc[1];
        double dj = lc[1] - j;
        int k = (int)lc[2];
        double dk = lc[2] - k;

        // deposit fractional values to the 8 surrounding nodes
        data[i][j][k] += value * (1 - di) * (1 - dj) * (1 - dk);
        data[i + 1][j][k] += value * (di) * (1 - dj) * (1 - dk);
        data[i + 1][j + 1][k] += value * (di) * (dj) * (1 - dk);
        data[i][j + 1][k] += value * (1 - di) * (dj) * (1 - dk);
        data[i][j][k + 1] += value * (1 - di) * (1 - dj) * (dk);
        data[i + 1][j][k + 1] += value * (di) * (1 - dj) * (dk);
        data[i + 1][j + 1][k + 1] += value * (di) * (dj) * (dk);
        data[i][j + 1][k + 1] += value * (1 - di) * (dj) * (dk);
    }

    friend std::ostream& operator<<(std::ostream& out, Field_<T>& f); // so data can be protected member of Field_

    const int ni, nj, nk;   // number of nodes

protected:
    T*** data;  // pointer of type T
};

template<typename T>
// output
std::ostream& operator<<(std::ostream& out, vec3<T>& v) {
    out << v[0] << " " << v[1] << " " << v[2];
    return out;
}


using Field = Field_<double>;   // field of doubles
using FieldI = Field_<int>;     // field of integers
using Field3 = Field_<double3>; // vector field of doubles
// Fields_.cpp
#include "Fields_.h"
Silas
  • 1
  • 4
  • You could simply be failing to compile the code containing the definitions of the missing functions. Among many other reasons. – john Jan 26 '21 at 13:59
  • Mismatch between declaration and definition is another strong possibility. I notice your operator<< are defined without const which is not ideal. Check that the definition and declaration **both** do not have const (or both do). – john Jan 26 '21 at 14:01
  • The compiling process seems to be fine, but I don't know how to double check. I double checked for a mismatches between the definition declaration but couldn't find any. I also tried adding `const` (for both) but that didn't help. So I changed back to code without `const` since the original doesn't have it either (and no rvalues seem to be called with this function). – Silas Jan 26 '21 at 14:11
  • OK, it's very simple, templates must be defined in header files. – john Jan 26 '21 at 14:14
  • I tried moving the whole `template std::ostream ...` to Output.h but it didn't change anything. @john is that what you meant? – Silas Jan 26 '21 at 14:18
  • That's what I meant. Seems like you have multiple problems. Try the same with `scatter`. – john Jan 26 '21 at 14:20
  • The simple way to check whether you are compiling a file is to put a deliberate error in the file. The error directive is useful for that `#error "can you see this?"` – john Jan 26 '21 at 14:21
  • Moving the template and definitions (or just template and declaration) to the .h files doesn't change anything. Though adding the `scatter` definition into the class itself, solved that error. `#error "can you see this?"` just stops the compiling where I placed it, printing it out as `satal error C1189: #error: "can you see this?"` – Silas Jan 26 '21 at 14:35
  • Well the error proves you are compiling the file. I don't have an explanation, maybe you are doing something wrong when you move the definitions to the header file, but I can't imagine what. It's interesting that for you inside the class works but outside doesn't. But both should work. All I can suggest is that you try to produce a minimal example and ask a new question. I've reopened this question because although templates and header files is part of the problem it's clear it's not the only problem. – john Jan 26 '21 at 14:51
  • Adding `operator<<`-overloading (and the `scatter` function) into the Fields_ class solved these errors. Now there are others issues, but this seems to be fixed. Thanks! – Silas Jan 26 '21 at 14:51
  • Well that's good but as I said it should be possible to define templates out of class as well. – john Jan 26 '21 at 14:52

1 Answers1

0

Adding operator<<-overloading (and the scatter function) into the Fields_ class solved these errors. Now there are others issues, but this seems to be fixed.

(@john: It should be possible to define templates out of class as well. But I don't know how in this example and this seems to be sufficient for my needs. If anyone knows a more elegant work around, I'm happy to try it out as well.)

Silas
  • 1
  • 4