2

The question is, why only the call to M::operator<< causes a link error, not the call to std::cout::operator<< when it should?

The code is as follows:

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

Compiling with g++ -g -O0 -std=c++11 -Wall -pedantic -Wextra wtf.cc results in

wtf_test.cc:25: undefined reference to `PJTest::fname'

Note that there is no error for line 28 when it should!

g++ -g -O2 -std=c++11 -Wall -pedantic -Wextra wtf.cc succeeds. (g++ 4.8.4 from Ubuntu 14.04LTS) and the behavior is the same with G++ 5.3.0

Compiling with clang++ always fails regardless of optimization level, but again, only for line 25; I know I can fix this by adding constexpr const char* PJTest::fname; but I want to understand WHY it causes a error in clang++.

wallabra
  • 412
  • 8
  • 17
Bulletmagnet
  • 5,665
  • 2
  • 26
  • 56
  • Possible duplicate of [Undefined reference to static constexpr char\[\]](http://stackoverflow.com/questions/8016780/undefined-reference-to-static-constexpr-char) – Jonas Schäfer Jan 22 '16 at 16:06
  • I know the fix is to add `constexpr const char* PJTest::fname;` , the question is why only the call to `M::operator<<` causes an error, not the call to `std::cout::operator<<`. – Bulletmagnet Jan 22 '16 at 16:13
  • 1
    In that case, please rephrase the question so that it is clear. – Jonas Schäfer Jan 22 '16 at 16:15

1 Answers1

4

The answer is - because std::ostream has non template version for const char* which is selected:

With such version also in your program:

    inline M& operator <<(const char* val) {
        std::cout << "str." << val;
        return *this;
    }

your code compile w/o problems.

More background - your real fname type is char[18] - so compiler best guess was:

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

As you can see - reference is required in this version - more or less this means fname shall have an address - it cannot be true optimized-out const.

You might also give this variable an address by defining it - like any other static variable of a class:

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

The overload resulation is really tough subject - most of the details are here, with template as new complication level - read here.

Just to make things a little simpler - let investigate much simpler form:

  1. Fail to link - because f(int const&) is selected - it requires "address"

Code:

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. All fine - const int converted to const double - not "address" needed:

Code:

class PJTest
{
public:
    static auto constexpr fvalue = 113;
};
//decltype(PJTest::fname) constexpr PJTest::fname;

void f(double) {}

int main() {
    f(PJTest::fvalue);
}
  1. Compiles fine - because non-template version is selected - non-template versions are always matched as first choice (this is more or less the std::ostream case - and my advice how to change your "stream" class):

Code:

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. Fails to link - because we have only template version - and it requires "address" - this equivalent to your version from question:

Code:

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);
}
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • This however, does not answer why a call to `std::cout` with `fname` doesn't give an undefined reference linker error. – Hatted Rooster Jan 22 '16 at 16:17
  • @JameyD - `fname` is the true const then. Const converted is still const. We do not need reference - only value - so we do not need the outside definition of `fname`. – PiotrNycz Jan 22 '16 at 16:28