C++ 20 Modules

C++ 20 Modules

I have been experimenting with C++ 20 modules.

ZIP file of my C++ 20 modules experiment.

I converted my logging routine over to a module. Preprocessor macros for source file information were replaced with std::source_location. Output streams are now passed as template arguments to enable normal messages to pass to std::cout and errors to std::cerr.

This module was compiled using:

  • CLang 17
  • Ninja 1.11.1
  • CMake 3.28
  • CLion 2023.3.

The module is named in Java like syntax using my URL to make a unique module name. Due to the lack of availability of a modularized standard library, I've included the headers rather than imports.

I find C++ 20 modules very interesting and will revisit them as they mature.

This is the output of my sample program.

output

INFO:/home/steve/Projects/modules/main.cpp:11:int main():start
DEBUG:/home/steve/Projects/modules/main.cpp:13:int main():hello
DEBUG:/home/steve/Projects/modules/main.cpp:13:int main():world
INFO:/home/steve/Projects/modules/main.cpp:14:int main():try
WARN:/home/steve/Projects/modules/main.cpp:15:int main():about to throw
THROW:/home/steve/Projects/modules/main.cpp:16:int main():throw fit
INFO:/home/steve/Projects/modules/main.cpp:21:int main():end
ERROR:/home/steve/Projects/modules/main.cpp:19:int main():caught an error

Process finished with exit code 0

The source files follow.

main.cpp

#include <iostream>

import com.threedbb.ulog;

#define LDEBUG LDebug()
#define LINFO LInfo()
#define LWARN LWarn()
#define LERROR LError()

int main() {
    LINFO << "start";
    try {
        LDEBUG << "hello\nworld\n";
        LINFO << "try";
        LWARN << "about to throw";
        LTHROW("throw fit");
    } catch (std::exception &e) {
        std::cout << e.what() << "\n";
        LERROR << "caught an error";
    }
    LINFO << "end\n";
    return 0;
}

C++ Module com.threedbb.ulog.ixx

module;

#include <algorithm>
#include <iostream>
#include <limits>
#include <locale>
#include <ranges>
#include <source_location>
#include <sstream>
#include <string>
#include <vector>

export module com.threedbb.ulog;

template<size_t N>
struct StringLiteral {
    constexpr StringLiteral(const char (&str)[N]) {
        std::copy_n(str, N, value);
    }

    char value[N];
};

struct Ostream {
    constexpr Ostream(std::ostream &os) : value(os) {}

    std::ostream &value;
};

template<::StringLiteral LIT, Ostream OUT>
class ULog;

export std::string toUTF8(const std::u16string &str);

export using LDebug = ULog<"DEBUG", std::cout>;
export using LInfo = ULog<"INFO", std::cout>;
export using LWarn = ULog<"WARN", std::cout>;
export using LError = ULog<"ERROR", std::cerr>;

export void LTHROW(
    const std::string &msg,
    const std::source_location &loc = std::source_location::current());

std::string asLogFormat(
    const std::string &level,
    const std::string &msg,
    const std::source_location &loc);

template<StringLiteral LIT, Ostream OUT>
class ULog {
private:
    std::ostream &output = OUT.value;
    std::string m_logLevel = LIT.value;
    std::source_location m_loc;
    std::stringstream m_ss;

public:
    explicit ULog(const std::source_location &loc =
    std::source_location::current()) : m_loc(loc) {}

    ~ULog() {
        std::string msg;
        while (std::getline(m_ss, msg, '\n')) {
            output << asLogFormat(m_logLevel, msg, m_loc) << "\n";
        }
    }

    template<typename T>
    ULog &operator<<(T const &Xi_val) {
        m_ss << Xi_val;
        return *this;
    }

    ULog &operator<<(const std::u16string &u16) {
        m_ss << toUTF8(u16);
        return *this;
    }

    ULog &operator<<(std::ostream &(*f)(std::ostream &)) {
        if (f == static_cast<decltype(f)>(std::endl)) {
            m_ss << '\n';
        }
        return *this;
    }

    std::ostream &os() {
        return m_ss;
    }
};

com.threedbb.ulog.impl.cpp

module;

#include <exception>
#include <algorithm>
#include <codecvt>
#include <locale>
#include <ranges>
#include <source_location>
#include <string>
#include <vector>
#include <fmt/core.h>

module com.threedbb.ulog;

std::string toUTF8(const std::u16string &str) {
    std::wstring_convert<
    std::codecvt_utf8_utf16<char16_t>,
    char16_t> convert;
    return convert.to_bytes(str);
}

void LTHROW(const std::string &msg, const std::source_location &loc) {
    throw std::runtime_error(asLogFormat("THROW",msg,loc));
}

std::string asLogFormat(
    const std::string &level,
    const std::string &msg,
    const std::source_location &loc) {
    return fmt::format("{}:{}:{}:{}:{}",
                       level,
                       loc.file_name(),
                       loc.line(),
                       loc.function_name(),
                       msg);
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.28)
project(modules)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)

add_compile_options(-fexperimental-library)
link_libraries(fmt)

add_library(mylog com.threedbb.ulog.impl.cpp)

target_sources(mylog
        PUBLIC
        FILE_SET CXX_MODULES FILES
        com.threedbb.ulog.ixx)

add_executable(modules main.cpp)
target_link_libraries(modules mylog)

C++ STL from JSON TTF Font Rendering