在模板 class 内声明的朋友模板函数导致未定义的符号 link 错误
Friend template function declared inside template class causing undefined symbol link error
几天来我一直在苦思冥想,查找它并在开源项目中寻找类似的代码:真的找不到我做错了什么。
基本上,给定以下代码(提炼出其本质):
#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();
}
在这样的代码中使用时:
int main(int argc, char* argv[]) {
Node<string> node("node X");
cout << node << endl;
Node<int> three(3);
cout << three << endl;
return EXIT_SUCCESS;
}
我收到以下链接器错误:
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)
据我所知,以上都是合法的C++11代码;模板定义明确,但它似乎以某种方式逃避了链接器找到它的能力。
这是在 OS X:
上使用 cmake 构建的
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})
什么给了?
提前致谢!
更新 在这个问题之后,我也 运行 以下内容,结果相同:
$ 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)
class as friend 里面的声明是非模板函数,class 外面的定义是模板函数,它们不匹配。而对于 overload resolution,非模板函数将在模板函数特化之前被选中,这就是为什么会出现未定义符号 link 错误。
您可以将声明更改为模板函数:
template<typename X>
friend std::ostream& operator <<(std::ostream& out, const Node<X>& node);
或者在class里面定义函数:
friend
std::ostream& operator <<(std::ostream& out, const Node<T>& node) { return out << node.value(); }
您的定义将匹配您没有的模板化好友声明。
有时您只想允许非常特定的 Node<T>
类型,所以您可以这样做。
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_; }
或者简单地改变你的定义,让它成为一个模板化的朋友。
大致有三种方法可以为您的 class 模板重载 operator<<
Node<T>
:
- 既然你提供了一个
public
成员函数value()
,你实际上并不需要一个friend
,而是可以完全用术语定义一个非友元非成员函数模板Node<T>
的 public
接口
template <typename T>
std::ostream& operator <<(std::ostream& out, const Node<T>& node) {
return out << node.value();
}
- 对于
Node<T>
的每个特化,您可以使用您定义 in-class 的非成员函数(并且它将存在于包含您的 class 模板的名称空间中)
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_;
}
};
- 对于
Node<T>
的每个特化,您可以通过使用 operator<< <>
语法(或等效的 operator<< <T>
声明 friend
来定义函数模板的伴随特化语法)
// 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_;
}
还有第四种可能,将template <typename U> operator<<(std::ostream&, const Node<U>&)
到class模板的所有特化Node<T>
交友(@songyuanyao回答的第一个选项),但我认为太过分了。
如果您可以根据 public 接口表达 I/O,我建议使用选项 1,否则使用选项 2 或 3。这两者大部分是等价的,在参数推导期间的名称查找和隐式转换方面有一些细微差别。
几天来我一直在苦思冥想,查找它并在开源项目中寻找类似的代码:真的找不到我做错了什么。
基本上,给定以下代码(提炼出其本质):
#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();
}
在这样的代码中使用时:
int main(int argc, char* argv[]) {
Node<string> node("node X");
cout << node << endl;
Node<int> three(3);
cout << three << endl;
return EXIT_SUCCESS;
}
我收到以下链接器错误:
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)
据我所知,以上都是合法的C++11代码;模板定义明确,但它似乎以某种方式逃避了链接器找到它的能力。
这是在 OS X:
上使用 cmake 构建的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})
什么给了?
提前致谢!
更新 在这个问题之后,我也 运行 以下内容,结果相同:
$ 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)
class as friend 里面的声明是非模板函数,class 外面的定义是模板函数,它们不匹配。而对于 overload resolution,非模板函数将在模板函数特化之前被选中,这就是为什么会出现未定义符号 link 错误。
您可以将声明更改为模板函数:
template<typename X>
friend std::ostream& operator <<(std::ostream& out, const Node<X>& node);
或者在class里面定义函数:
friend
std::ostream& operator <<(std::ostream& out, const Node<T>& node) { return out << node.value(); }
您的定义将匹配您没有的模板化好友声明。
有时您只想允许非常特定的 Node<T>
类型,所以您可以这样做。
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_; }
或者简单地改变你的定义,让它成为一个模板化的朋友。
大致有三种方法可以为您的 class 模板重载 operator<<
Node<T>
:
- 既然你提供了一个
public
成员函数value()
,你实际上并不需要一个friend
,而是可以完全用术语定义一个非友元非成员函数模板Node<T>
的
public
接口
template <typename T>
std::ostream& operator <<(std::ostream& out, const Node<T>& node) {
return out << node.value();
}
- 对于
Node<T>
的每个特化,您可以使用您定义 in-class 的非成员函数(并且它将存在于包含您的 class 模板的名称空间中)
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_;
}
};
- 对于
Node<T>
的每个特化,您可以通过使用operator<< <>
语法(或等效的operator<< <T>
声明friend
来定义函数模板的伴随特化语法)
// 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_;
}
还有第四种可能,将template <typename U> operator<<(std::ostream&, const Node<U>&)
到class模板的所有特化Node<T>
交友(@songyuanyao回答的第一个选项),但我认为太过分了。
如果您可以根据 public 接口表达 I/O,我建议使用选项 1,否则使用选项 2 或 3。这两者大部分是等价的,在参数推导期间的名称查找和隐式转换方面有一些细微差别。