2

I am working on a template Graph data structure, which is an STL vector of GraphNode objects. I defined the GraphNode class nested inside the Graph class and when I invoke the overloaded insertion operator for the GraphNode object inside the overloaded insertion operator for the Graph object Visual Studio 15 (C++) reports,

(30): warning C4346: 'myGraph<T>::myGraphNode': dependent name is not a type 
(30): note: prefix with 'typename' to indicate a type 
(30): error C2061: syntax error: identifier 'myGraphNode' 
(33): error C2805: binary 'operator <<' has too few parameters 
template <typename T>
ostream& operator<<(ostream& strm, const myGraph<T>::myGraphNode& gn)

adding the word typename to the second formal parameter

template <typename T>
ostream& operator<<(ostream& strm, typename const myGraph<T>::myGraphNode& gn)

The compiler generates the following error

(49): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const myGraph<int>::myGraphNode' (or there is no acceptable conversion)

I get the same error if I typename const .... or const typename ...

For completeness here is all the code somewhat simplified for this post. Thanks for any help

#include <iostream>
#include <vector>
#include <string>

using namespace std;

typedef unsigned int uint;

template <typename T>
class myGraph {
public:
    class myGraphNode {
    public:
        myGraphNode(T val = T());
        T mData;
    }; // end class myGraphNode

    myGraph();

    uint addGraphNode(T data);
    vector<myGraphNode> mGraphNodes;
}; // end class myGraph


//          myGraphNode
template <typename T>
myGraph<T>::myGraphNode::myGraphNode(T val) : mData(val) {}

template <typename T>
ostream& operator<<(ostream& strm, typename const myGraph<T>::myGraphNode& gn) {
    strm << gn.mData << std::endl;
    return strm;
}


//          myGraph
template <typename T>
myGraph<T>::myGraph() {}

template <typename T>
uint myGraph<T>::addGraphNode(T data) {
    myGraph<T>::myGraphNode node(data);
    mGraphNodes.push_back(node);
}

template <typename T>
ostream& operator<<(ostream& strm, const myGraph<T>& g) {
    for (uint i = 0; i < g.mGraphNodes.size(); ++i)
        cout << g.mGraphNodes[i] << endl;
    return strm;
} // end operator<<(...)

int main()
{
    myGraph<int> g;
    g.addGraphNode(3);
    g.addGraphNode(5);
    cout << g << endl;
    return 0;
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • The problem stems from the fact that when the parser encounters `myGraph::myGraphNode`, since `myGraph` is a template, it can't know (in general) whether `myGraphNode` refers to a type, an object, or something else without instantiating the template for some `T` beforehand. It has therefore been decided it should be interpreted as a value "by default", and that you need to add typename (like this: `typename myGraph::myGraphNode`) to explicitly tell the parser this is a type. – Caninonos Mar 31 '18 at 12:20

2 Answers2

1

Firstly, the correct syntax for parameter declaration should be

template <typename T>
ostream& operator<<(ostream& strm, const typename myGraph<T>::myGraphNode& gn)
//                                 ~~~~~ ~~~~~~~~

Refer here for more informations.

Secondly, with the above declaration, when trying to call it in operator<< for myGraph<T> like cout << g.mGraphNodes[i] << endl;, T can't be deduced because of non-deduced contexts):

The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:

That means, you have to specify the template argument explicitly for it, e.g.

operator<<<T>(strm, g.mGraphNodes[i]);
//        ~~~

But it's ugly. For your case, you can just implement operator<< for myGraph<T> like

template <typename T>
ostream& operator<<(ostream& strm, const myGraph<T>& g) {
    for (uint i = 0; i < g.mGraphNodes.size(); ++i)
        cout << g.mGraphNodes[i].mData << endl;
    return strm;
}

BTW: You should give a return value for myGraph<T>::addGraphNode.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Thanks but I want to overload the insertion operator for myGraphNode objects instead of making the myGraph class understand about the myGraphNode implementation. I know there are work arounds for this problem I would like to better understand the problem. I am trying to follow the technique in the link of the first response but seem to be having difficulty understanding exactly what to do in my specific case. Thanks for any explanation and guidance. – studentProgrammer Mar 31 '18 at 12:31
  • Thanks. the operator<<(strm, g.mGraphNodes[i]); works but is here any way I can invoke it as I would for any of the primitive data types? strm << g.mGraphNodes[i] << endl; instead of explicitly calling the overloaded function with a ? – studentProgrammer Mar 31 '18 at 12:39
  • @studentProgrammer So you still have trouble in understanding the 1st issue? (i.e. the one about using `typename`). – songyuanyao Mar 31 '18 at 12:41
  • @studentProgrammer For the 2nd issue, as far as i can think there's no way; you have to specify the template argument like `operator<<` because of non-deduced context. – songyuanyao Mar 31 '18 at 12:42
  • I still get the compiler error when I do the typename as you suggest – studentProgrammer Mar 31 '18 at 12:51
  • @studentProgrammer What's the error message? I tried a live demo [here](https://wandbox.org/permlink/3Vxlk5DZqQgyxOPC) and it compiles fine. – songyuanyao Mar 31 '18 at 12:58
  • the change is:(51): ostream& operator<<(ostream& strm, const typename myGraph& g) and I get error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const myGraph::myGraphNode' (or there is no acceptable conversion) – studentProgrammer Mar 31 '18 at 13:04
  • @studentProgrammer Did you try with code I posted in live demo? If so and you still get the error, it might be a compiler issue.. – songyuanyao Mar 31 '18 at 13:08
  • songyuanyao, Yes, it works as operator<<(...) but not as strm << mGraphNodes[i] << endl; for any compiler. I guess operator<<(...) is the only possible solution because of the dependency. Many thanks for all your help. – studentProgrammer Mar 31 '18 at 17:05
1

Template type deduction only matches patterns. It doesn't invert dependent types, because that (in the general case) is impossible.

The way to solve this problem is a technique I call Koenig operators.

friend std::ostream& operator<<(std::ostream& strm, const myGraphNode& gn) {
  strm << gn.mData << std::endl;
  return strm;
}

Put this in the body of myGraphNode.

class myGraphNode {
public:
    myGraphNode(T val = T());
    T mData;
    friend std::ostream& operator<<(std::ostream& strm, const myGraphNode& gn) {
      strm << gn.mData << std::endl;
      return strm;
    }
}; // end class myGraphNode

This is a non-template operator injected into surrounding namespace (only) reachable via ADL. Which is fancy words for "it just works".

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks Yakk this also works. I was trying to keep the implementation of the function out of the definition because some of the functions get rather long. Is it possible to put the implementation of operator<<(std::ostream& strm, const myGraphNode& gn) in the global namespace instead of the Graph? – studentProgrammer Mar 31 '18 at 14:06
  • @student forward to another method. Like `void print(std::ostream&);` – Yakk - Adam Nevraumont Mar 31 '18 at 14:32