Marco Massenzio
Marco Massenzio

Reputation: 3002

Friend template function declared inside template class causing undefined symbol link error

I have been banging my head against this for a couple of days, looking it up and also looking for similar code in open source projects: can't really find what I'm doing incorrectly.

Essentially, given the code below (distilled to its essence):

#include <iostream>


using std::cout;
using std::endl;
using std::string;

template <typename T>
class Node {
    T value_;
public:
    Node(const T& value) : value_(value) {}
    T const value() const { return value_; }

    friend
    std::ostream& operator <<(std::ostream& out, const Node<T>& node);

    Node<T> operator +(const Node<T>& other) {
        return Node(value() + other.value());
    }
};


template <typename T>
std::ostream& operator <<(std::ostream& out, const Node<T>& node) {
    return out << node.value();
}

when used in code such as this:

int main(int argc, char* argv[]) {

    Node<string> node("node X");
    cout << node << endl;

    Node<int> three(3);
    cout << three << endl;

    return EXIT_SUCCESS;
}

I get the following linker error:

Undefined symbols for architecture x86_64:
  "operator<<(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, Node<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > const&)", referenced from:
      _main in StlPractice.cpp.o
  "operator<<(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, Node<int> const&)", referenced from:
      _main in StlPractice.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

As far as I can tell, the above is all legal C++11 code; the template is well-defined, and yet, it seems to somehow escape the ability of the linker to find it.

This is built using cmake on OS X:

cmake_minimum_required(VERSION 3.3)
project(simple_template)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

set(SOURCE_FILES src/simple_template.cpp)

add_executable(simple ${SOURCE_FILES})

What gives?

Thanks in advance!

Update Following the question, I've also ran the following, same result:

$ clang++ src/simple_template.cpp 
Undefined symbols for architecture x86_64:
  "operator<<(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, Node<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > const&)", referenced from:
      _main in StlPractice-e20370.o
  "operator<<(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, Node<int> const&)", referenced from:
      _main in StlPractice-e20370.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Upvotes: 6

Views: 1224

Answers (3)

TemplateRex
TemplateRex

Reputation: 70526

There are roughly three ways to overload operator<< for your class template Node<T>:

  1. Since you provide a public member function value(), you don't actually need a friend but can instead define a non-friend non-member function template entirely in terms of the public interface of Node<T>

Live Example 1:

template <typename T>
std::ostream& operator <<(std::ostream& out, const Node<T>& node) {
    return out << node.value();
}
  1. For each specialization of Node<T>, you can use a non-member function that you define an in-class (and that will live in the namespace enclosing your class template)

Live Example 2:

template <typename T>
class Node {        
    // generates a non-template operator<< for this T
    friend std::ostream& operator<<(std::ostream& out, const Node<T>& node) {
        return out << node.value_;
    }   
};
  1. For each specialization of Node<T>, you can define an accompanying specialization of a function template by declaring the friend using the operator<< <> syntax (or the equivalent operator<< <T> syntax)

Live Example 3

// forward declare to make function declaration possible
template <typename T>
class Node;

// declaration
template <typename T>
std::ostream& operator <<(std::ostream& out, const Node<T>& node);

template <typename T>
class Node {
    // refers to a full specialization for this particular T 
    friend std::ostream& operator<< <>(std::ostream& out, const Node<T>& node);    
    // note: this relies on template argument deduction in declarations
    // can also specify the template argument with operator<< <T>"    
};

// definition
template <typename T>
std::ostream& operator <<(std::ostream& out, const Node<T>& node) {
    return out << node.value_;
}

There is also a fourth possibility by befriending all specializations of an template <typename U> operator<<(std::ostream&, const Node<U>&) to class template Node<T> (the first option of the answer by @songyuanyao) but in my opinion that is overkill.

I would recommend using option 1 if you can express I/O in terms of the public interface, and option 2 or 3 otherwise. These two are mostly equivalent, with some minor differences in name lookup and implicit conversions during argument deduction.

Upvotes: 1

songyuanyao
songyuanyao

Reputation: 172924

The declaration inside the class as friend is non-template function, and the definition outside the class is template function, they don't match. And for overload resolution, non-template function will be selected prior to template function specialization, that's why undefined symbols link error occured.

You could change the declaration as template function:

template<typename X>
friend std::ostream& operator <<(std::ostream& out, const Node<X>& node);

LIVE

or define the function inside the class:

friend 
std::ostream& operator <<(std::ostream& out, const Node<T>& node) { return out << node.value(); }

LIVE

Upvotes: 9

Jts
Jts

Reputation: 3527

Your definition would match a templated friend declaration, which you don't have.

Sometimes you only want to allow very specific Node<T> types, so this is how you would do it.

http://rextester.com/GZKCJQ35441

template <typename T>
class Node {
    T value_;
public:
    Node(const T& value) : value_(value) {}
    T const value() const { return value_; }

    friend 
    std::ostream& operator <<(std::ostream& out, const Node<T>& node);

    Node<T> operator +(const Node<T>& other) {
        return Node(value() + other.value());
    }
};

std::ostream& operator <<(std::ostream& out, const Node<int>& node) { return out << node.value_; }
std::ostream& operator <<(std::ostream& out, const Node<string>& node) { return out << node.value_; }

Or simply change your definition and make it a templated-friend.

Upvotes: 1

Related Questions