c++visual-c++g++clang++manipulators

Custom manipulator compiles with Visual C++ but not g++/clang


I have a logger class QueuedLog which queues up log messages and inserts all the log message(s) in the queue into a std::ostream if and when desired. To separate each log message, I wrote a manipulator called endm which is used in a similar manner as std::endl. For example, here's a use case:

QueuedLog log(INFO);

// do stuff

log << "test: 0x" << std::hex << std::uppercase << 15 << endm; // add a log message

// do more stuff

log << "log something else" << endm;

std::cout << log << std::endl; // insert all the queued up log messages into cout

// do more stuff

log << "start another message...";

// calculate something, add it to a log message and end the message
log << std::dec << 42 << endm;

std::cout << log << std::endl; // insert all the queued up log messages into cout

log << "add yet another message" << endm;

// don't need to log that last message after all
// leave it in the QueuedLog instead of inserting it into cout

My code compiles fine with Visual C++ but g++ and clang++ fail compilation when I try to use the endm manipulator. Here is a minimal version of the QueuedLog (which usually resides in a separate header file), with a small example usage demonstrating the problem:

#include <ios>
#include <iostream>
#include <string>
#include <sstream>
#include <deque>
#include <stdexcept>

namespace Logger {

enum LogType {
    NONE,   
    DEBUG,  
    INFO,   
    WARN,   
    ERROR,  
    FATAL   
};

// Converts a LogType to a `std::string` which can be prepended to a log message.
std::string prepend_type(LogType type) {
    switch (type) {
        case DEBUG: return std::string("[DEBUG] ");
        case INFO: return std::string("[INFO] ");
        case WARN: return std::string("[WARN] ");
        case ERROR: return std::string("[ERROR] ");
        case FATAL: return std::string("[FATAL] ");
        default: return std::string("");
    }
}

class QueuedLog {
    /* Holds a partially contructed log message.

    A std::stringstream is used instead of a std::string so that non-string data types can be inserted into
    the QueuedLog without requiring conversion. Also, client code can apply I/O manipulators like std::hex.
    */
    std::ostringstream stream;

    std::deque<std::string> messages; // Holds the queued, completed log message(s).

    // The LogType of new messages inserted into the QueuedLog. This can be changed at any time.
    LogType log_type;
public:
    QueuedLog(LogType logtype = NONE) : log_type(logtype) {} // Constructs a QueuedLog with no text and an initial LogType.

    // Inserts a character sequence into the QueuedLog.
    template<typename T> inline QueuedLog& operator<<(const T& message) {
    //inline QueuedLog& operator<<(const std::string& message) { // non-template version doesn't work, either
        // Only prepend with logtype if it is the beginning of the message
        if (stream.str().empty()) stream << prepend_type(log_type);

        stream << message;
        return *this;
    }

    // Overload used for manipulators like QueuedLog::endm()
    inline QueuedLog& operator<<(QueuedLog& (*pf)(QueuedLog&)) {
        (*pf)(*this);
        return *this;
    }

    // Adds the newline character and marks the end of a log message.
    friend inline QueuedLog& endm(QueuedLog& log) {
        log.stream << log.stream.widen('\n');

        // Add the completed message to the messages deque, and reset the stream for the next message
        log.messages.push_back(log.stream.str());
        log.stream.str(""); // clear the underlying string
        log.stream.clear(); // clear any error flags on the stream

        return log;
    }

    /* Inserts all the completed log messages in the QueuedLog object into a std::ostream.

    If the QueuedLog contains an incomplete log message (a message that has not been terminated by QueuedLog::endm())
    then that partial message will not be inserted into the std::ostream.
    */
    friend inline std::ostream& operator<<(std::ostream& os, QueuedLog& log) {
        while (!log.messages.empty()) {
            os << log.messages.front();
            log.messages.pop_front();
        }

        return os;
    }
};

} // end namespace Logger

using namespace Logger;

int main() {
    QueuedLog log(INFO);

    log << "test: 0x" << std::hex << std::uppercase << 15; // compiles by itself with all compilers
    log << endm; // but compilation error w/ g++/clang++ when trying to use endm
    std::cout << log << std::endl;
}

Or, this (possibly over-)simplified example:

class QueuedLog {
public:
    friend inline void endm() {
    }
};

int main() {
    endm;
}

I've attempted to compile it with all three compilers at rextester but it only compiles successfully with Visual C++.

g++ gives the following error:

error: ‘endm’ was not declared in this scope

The error message from clang++ is similar:

error: use of undeclared identifier 'endm'

Why does this work in Visual C++ but not g++ or clang++? How do I fix it for g++/clang++? The solution does not need to work in all three compilers simultaneously, I just want to know how to fix it for g++ and clang++.


Solution

  • friend functions defined inside a class definition are only visible inside that class definition. You need to declare the function outside the class definition. Add this line outside your class QueuedLog, but inside your namespace Logger:

    extern QueuedLog& endm(QueuedLog&);