为什么 M::operator<< 导致 link 错误而不是 std::cout::operator<<
Why M::operator<< causes a link error instead of std::cout::operator<<
问题是,为什么只有对 M::operator<<
的调用会导致 link 错误,而不是对 std::cout::operator<<
的调用?
代码如下:
#include <iostream>
struct M {
inline M() {}
template <typename T>
inline M& operator <<(const T& val) {
std::cout << "ref." << val;
return *this;
}
template <typename T>
inline M& operator <<(T* const& pointer) { // NOLINT
std::cout << "ptr." << pointer;
return *this;
}
};
class PJTest
{
public:
~PJTest()
{
M()
<< "Failed to remove file '" << fname << "' because: stuff\n"; // 25
std::cout
<< "Failed to remove file '" << fname << "' because: stuff\n"; // 28
}
protected:
static auto constexpr fname = "what's in a name?";
};
int main() {
PJTest pt;
}
用 g++ -g -O0 -std=c++11 -Wall -pedantic -Wextra wtf.cc
编译结果
wtf_test.cc:25: undefined reference to `PJTest::fname'
请注意第 28 行没有错误!
g++ -g -O2 -std=c++11 -Wall -pedantic -Wextra wtf.cc
成功。
(来自 Ubuntu 14.04LTS 的 g++ 4.8.4)并且行为与 G++ 5.3.0
相同
无论优化级别如何,使用 clang++ 编译总是失败,但同样,仅针对第 25 行;我知道我可以通过添加 constexpr const char* PJTest::fname;
来解决这个问题,但我想了解 为什么它会导致 clang++.
出错
答案是 - 因为 std::ostream
有选择的 const char*
的非模板版本:
你的程序中也有这样的版本:
inline M& operator <<(const char* val) {
std::cout << "str." << val;
return *this;
}
您的代码编译 w/o 问题。
更多背景 - 你真正的 fname
类型是 char[18]
- 所以编译器的最佳猜测是:
template <typename T>
inline M& operator <<(const T& val) {
std::cout << "ref." << val;
return *this;
}
如您所见 - 此版本中需要引用 - 或多或少这意味着 fname
应该有一个地址 - 它不可能是 true optimized-out const。
你也可以通过定义它来给这个变量一个地址 - 就像 class:
的任何其他静态变量一样
class PJTest
{
//....
protected:
static auto constexpr fname = "what's in a name?";
};
decltype(PJTest::fname) constexpr PJTest::fname;
过载限制是一个非常棘手的课题 - 大多数细节都是 here, with template as new complication level - read here。
只是为了让事情更简单一些 - 让我们研究更简单的形式:
- 无法 link - 因为选择了
f(int const&)
- 它需要 "address"
代码:
class PJTest
{
public:
static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
void f(const int&) {}
void f(double) {}
int main() {
f(PJTest::fvalue);
}
- 没问题 -
const int
转换为 const double
- 不需要 "address":
代码:
class PJTest
{
public:
static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
void f(double) {}
int main() {
f(PJTest::fvalue);
}
- 编译良好 - 因为选择了 non-template 版本 - non-template 版本总是作为首选匹配(这或多或少是 std::ostream 的情况 - 我的建议是如何改变你的 "stream" class):
代码:
class PJTest
{
public:
static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
template <typaname T>
void f(const T&) {}
void f(double) {}
int main() {
f(PJTest::fvalue);
}
- 未能达到 link - 因为我们只有模板版本 - 它需要 "address" - 这相当于你的问题版本:
代码:
class PJTest
{
public:
static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
template <typaname T>
void f(const T&) {}
int main() {
f(PJTest::fvalue);
}
问题是,为什么只有对 M::operator<<
的调用会导致 link 错误,而不是对 std::cout::operator<<
的调用?
代码如下:
#include <iostream>
struct M {
inline M() {}
template <typename T>
inline M& operator <<(const T& val) {
std::cout << "ref." << val;
return *this;
}
template <typename T>
inline M& operator <<(T* const& pointer) { // NOLINT
std::cout << "ptr." << pointer;
return *this;
}
};
class PJTest
{
public:
~PJTest()
{
M()
<< "Failed to remove file '" << fname << "' because: stuff\n"; // 25
std::cout
<< "Failed to remove file '" << fname << "' because: stuff\n"; // 28
}
protected:
static auto constexpr fname = "what's in a name?";
};
int main() {
PJTest pt;
}
用 g++ -g -O0 -std=c++11 -Wall -pedantic -Wextra wtf.cc
编译结果
wtf_test.cc:25: undefined reference to `PJTest::fname'
请注意第 28 行没有错误!
g++ -g -O2 -std=c++11 -Wall -pedantic -Wextra wtf.cc
成功。
(来自 Ubuntu 14.04LTS 的 g++ 4.8.4)并且行为与 G++ 5.3.0
无论优化级别如何,使用 clang++ 编译总是失败,但同样,仅针对第 25 行;我知道我可以通过添加 constexpr const char* PJTest::fname;
来解决这个问题,但我想了解 为什么它会导致 clang++.
答案是 - 因为 std::ostream
有选择的 const char*
的非模板版本:
你的程序中也有这样的版本:
inline M& operator <<(const char* val) {
std::cout << "str." << val;
return *this;
}
您的代码编译 w/o 问题。
更多背景 - 你真正的 fname
类型是 char[18]
- 所以编译器的最佳猜测是:
template <typename T>
inline M& operator <<(const T& val) {
std::cout << "ref." << val;
return *this;
}
如您所见 - 此版本中需要引用 - 或多或少这意味着 fname
应该有一个地址 - 它不可能是 true optimized-out const。
你也可以通过定义它来给这个变量一个地址 - 就像 class:
的任何其他静态变量一样class PJTest
{
//....
protected:
static auto constexpr fname = "what's in a name?";
};
decltype(PJTest::fname) constexpr PJTest::fname;
过载限制是一个非常棘手的课题 - 大多数细节都是 here, with template as new complication level - read here。
只是为了让事情更简单一些 - 让我们研究更简单的形式:
- 无法 link - 因为选择了
f(int const&)
- 它需要 "address"
代码:
class PJTest
{
public:
static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
void f(const int&) {}
void f(double) {}
int main() {
f(PJTest::fvalue);
}
- 没问题 -
const int
转换为const double
- 不需要 "address":
代码:
class PJTest
{
public:
static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
void f(double) {}
int main() {
f(PJTest::fvalue);
}
- 编译良好 - 因为选择了 non-template 版本 - non-template 版本总是作为首选匹配(这或多或少是 std::ostream 的情况 - 我的建议是如何改变你的 "stream" class):
代码:
class PJTest
{
public:
static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
template <typaname T>
void f(const T&) {}
void f(double) {}
int main() {
f(PJTest::fvalue);
}
- 未能达到 link - 因为我们只有模板版本 - 它需要 "address" - 这相当于你的问题版本:
代码:
class PJTest
{
public:
static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
template <typaname T>
void f(const T&) {}
int main() {
f(PJTest::fvalue);
}