forward declaration confusion


I am unsure where to place forward declarations. Is it possible not to place them in the header file?

Reason I ask is because the following compile error occurs:
1
2
3
4
5
6
c++ -g -std=c++14 -pedantic -Wall -Wpointer-arith -Wwrite-strings -Wcast-qual -Wcast-align -Wformat-security -Wformat-nonliteral -Wmissing-format-attribute -Winline -funsigned-char "use_tester.cpp" (in directory: /c++/tester)
use_tester.cpp: In function ‘int main()’:
use_tester.cpp:20:21: error: variable ‘tester<double> b’ has initializer but incomplete type
   tester<double> b(v);
                     ^
Compilation failed.


for the following code:
class declaration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#ifndef _TESTER_H_
#define _TESTER_H_

#include <memory>
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <initializer_list>

template <typename T>
class tester {
  private:
    const std::vector<T> vd;
    mutable std::unique_ptr<T> balance;

    inline double calc_balance() const {
      return std::accumulate(begin(vd), end(vd), 0.0);
    }

    // print results are cached, hence mutable
    mutable std::string generated_stream;
    mutable bool stream_generated {false};

    std::string print_elements(char sep) const {
      if (!stream_generated) {
        for (const auto & e : vd)
          generated_stream += (std::to_string(e) + sep);
        stream_generated = true;
      }
      return generated_stream;
    }

    void print(std::ostream & os) const {
      os << "<" << vd.size() << ": " << print_elements() << ">.";
    }

  public:
    explicit tester<T>(const std::vector<T> & test_balance ) : vd{ test_balance }, balance() { }
    ~tester<T>() { }

    inline T get_balance() const {
      if (!balance) balance = std::make_unique<T>(calc_balance());
      return *balance;
    }

    inline long double get_avg() const {
      if (!balance) balance = get_balance();
      return (*balance) / vd.size();
    }

    template <class T>
    friend std::ostream & operator<< (std::ostream & os, const tester<T> & tst);
};

#include "use_tester.cpp"
#endif 


definition
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <ctime>
#include <fstream>
#include <regex>
#include <string>
#include <iostream>

template <typename T> class tester;

template <class T>
std::ostream & operator<< (std::ostream & os, const tester<T> & tst);

template <class T>
std::ostream & operator<< (std::ostream & os, const tester<T> & tst) {
  tst.print(os);
  return os;
}

int main() {
  std::vector<double> v{12.2, 13.3, 99.3, 100.3, 101.33, -99};
  tester<double> b(v);
  std::cout << "Balance : " << b.get_balance() << " Avg : " << b.get_avg() << std::endl;

  return 0;
}
1. check your calc_balance(), get_balance() methods, if they return double that defeats the whole purpose of having templates in the first place; they should return T, not double to be true templates
2. templates - header - implementation combo is a rich area of fun, I'll leave you to decide how you want to do it as that is not the main focus of this thread (http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file)
3. let's focus on the main topic here: friend operator << - what you're trying to do is a bound template friend operator << to template class tester so that each specialization of tester gets a matching specialization of the friend operator. To get there you have to:
(a) forward declare tester;
(b) forward declare the friend operator;//if this did not take a tester argument (a) would not be necessary;
(c) now declare (and in your case also define since several of the methods are inline) the actual template class tester where the friend function is declared with a blank <> or with <T> which tells the compiler that operator << can only access tester<T> for the same T as is instantiating class tester (you can leave <> blank as the compiler already sees T at the class template declaration level)
4. Implement the operator
5. It is redundant to have print(), print_elements() and operator<< overload for the same class; I have removed the first two and everthing is handled by the insertion operator overload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>

using namespace std;

template <typename T> class tester;
template <class T>
ostream & operator<< (std::ostream & os, const tester<T> & tst);

template <typename T>
class tester {
  private:
    const vector<T> vd;
    mutable unique_ptr<T> balance;

    inline T calc_balance() const {
      return accumulate(vd.begin(), vd.end(), 0.0);
    }

    // print results are cached, hence mutable
    mutable string generated_stream{};
    mutable bool stream_generated {false};

     public:
    explicit tester<T>(const vector<T> & test_balance) : vd{ test_balance }, balance(nullptr) { }
    ~tester<T>() {}

    inline T get_balance() const {
      if (!balance) balance = std::make_unique<T>(calc_balance());
      return *balance;
    }

    inline  T get_avg() const {
     // if (!balance) balance = get_balance();
      return (calc_balance()) / vd.size();
    }

    friend std::ostream & operator<< <>(std::ostream & os, const tester<T> & tst);
};
int main()
{
    std::vector<double> v{12.2, 13.3, 99.3, 100.3, 101.33, -99};
    tester<double> b(v);
    cout<<b<<"\n";
}

template <class T>
std::ostream & operator<< (std::ostream & os, const tester<T> & tst)
{
    os<<"Balance entries are: \n";
    for(auto& elem : tst.vd)
    {
        os<<elem<<"\t";
    }
        os<<"\nBalance : " << tst.get_balance() << " \tAvg : " << tst.get_avg() <<"\n";
  return os;
}

Output
1
2
3
4
5
6
7
Balance entries are:
12.2    13.3    99.3    100.3   101.33  -99
Balance : 227.43        Avg : 37.905


Process returned 0 (0x0)   execution time : 0.031 s
Press any key to continue.


edit: within class declaration/definition not necessary to typename <T> the ctor/dtor
Last edited on
Of additional note is:
If you forward declare a class, you cannot directly store instances of that object there. You can, however, store pointers to objects of that class. You can include the header in the .cpp file if the code internally must have access to the implementation and have to avoid a circular include problem.

1
2
3
4
5
6
7
8
9
10
class SomeClass;

class OtherClass
{
  public:
    OtherClass() = default;
    ~OtherClass() = default;

    std::shared_ptr<SomeClass> PointersToClass;
}


But if that isn't what is stopping you, then just include the full class declaration where you need to use it.

Hi

thanks gunnerfunner.

A few points
1. did you mean to specify:
1
2
3
    inline  T get_avg() const {
      return (get_balance()) / vd.size();
    }

instead of
1
2
3
    inline  T get_avg() const {
      return (calc_balance()) / vd.size();
    }

2.
thanks for pointing out that calc_balance should return T instead of long double, but I deliberately left get_avg to return long double since if an INT were specified as tester<int> then the answer for get_avg would be incorrect since no floating point.

How would you go about getting the correct answer for get_avg if tester were created as tester<int> and return type for get_avg was T ?

3.
I am unsure of where the #include should occur, if tester class declaration is held a header file, say tester.h, then should #include "tester.h" be specified in the .cpp file, or should "tester_definition.cpp" be included in the header file, tester.h, as #include "tester_definition.cpp" below instead?
Does this effect the placement of forward declarations ?


Last edited on
1. as far as I can recall from my morning session with your code, get_balance() is not fully functional yet. If you can clean it up you can use that instead if you wish
2. I wouldn't get too bogged down by the CS_101 int vs double vs float debate, but be rather more excited that this class template works(!) and can be so powerful in handling custom data types. Just declare all numeric POD in your user defined types as doubles to keep it flexible for averages, etc and focus on how you could extend this even further (smart pointers is a good start, what about templates as parameters, member templates, variadic templates ... )
3. this can be quite system specific depending on your compiler. If you study the link I'd included in #2 of my reply the user selected best reply has suggestions about how to keep the implementation separate from the declaration in template classes. And this was pre C++11 so rummage around and see if you can find something more recent and applicable for your specific needs

thanks for replying.
1. understand your point, to do with the fact that the return types different
2. thanks, very good answer. I'll change all return types to T
3. i'll have to to do a bit of reading to come inline with C++11

thanks again!
Topic archived. No new replies allowed.