为什么 M::operator<< 会导致链接错误,而不是 std::cout::operator<<

Why M::operator<< causes a link error instead of std::cout::operator<<

本文关键字:lt operator std cout 为什么 链接 错误      更新时间:2023-10-16

问题是,为什么只有对M::operator<<的调用会导致链接错误,而不是在应该调用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: stuffn"; // 25
        std::cout
            << "Failed to remove file '" << fname << "' because: stuffn"; // 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;
    }

您的代码编译没有问题。

更多背景 - 您的真实fname类型是char[18] - 因此编译器的最佳猜测是:

    template <typename T>
    inline M& operator <<(const T& val) {
        std::cout << "ref." << val;
        return *this;
    }

如您所见 - 此版本中需要引用 - 或多或少这意味着fname应该有一个地址 - 它不可能是真正的优化常量。

你也可以通过定义这个变量来给它一个地址 - 就像类的任何其他静态变量一样:

class PJTest
{
//....
protected:
    static auto constexpr fname = "what's in a name?";
};
decltype(PJTest::fname) constexpr PJTest::fname;

过载绝缘是一个非常棘手的主题 - 大部分细节都在这里,模板作为新的复杂程度 - 阅读这里。

只是为了让事情更简单一点 - 让我们研究更简单的形式:

  1. 无法链接 - 因为选择了f(int const&) - 它需要"地址"

法典:

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);
}
  1. 一切都很好 - const int转换为const double - 不需要"地址":

法典:

class PJTest
{
public:
    static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;
void f(double) {}
int main() {
    f(PJTest::fvalue);
}
    编译很好 - 因为选择了非模板版本 -
  1. 非模板版本始终匹配为首选(这或多或少是 std::ostream 的情况 - 以及我如何更改"流"类的建议):

法典:

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);
}
  1. 无法链接 - 因为我们只有模板版本 - 并且它需要"地址" - 这相当于您的问题版本:

法典:

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);
}