How to use zig build for importing C++23's std?

I’m trying to use the println statement in C++23 by using the workaround as given here.

Am I missing some flags or function calls (like exe.linkLibCpp();) to make this work?


Additional information:

build.zig
const std = @import("std");

const CPP_ROOT_DIR = "./src/";

const CPP_FLAGS = &.{
    "-Wall",
    "-Werror",
    "-O2",
    "-std=c++23",
};

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "temp",
        .target = target,
        .optimize = optimize,
    });

    exe.addCSourceFiles(.{
        .root = b.path(CPP_ROOT_DIR),
        .files = &.{
            "main.cpp",
        },
        .flags = CPP_FLAGS,
    });

    exe.linkLibCpp();

    b.installArtifact(exe);
}
src/main.cpp
#include "print_workaround.h"

int main() {
    int answer { 42 };
    std::println("The answer to life, the universe, and everything is {}.", answer);
    return 0;
}

The workaround is src/print_workaround.h.

Error
/path/to/cpp23zig/src/print_workaround.h:2:8: error: module 'std' not found
import std;
~~~~~~~^
/path/to/cpp23zig/src/main.cpp:1:10: note: in file included from /path/to/cpp23zig/src/main.cpp:1:
#include "print_workaround.h"
^
error: the following command failed with 1 compilation errors:
$HOME/.local/share/zig/zig build-exe -cflags -Wall -Werror -O2 -std=c++23 -- /path/to/cpp23zig/src/main.cpp -ODebug -Mroot -lc++ --cache-dir /path/to/cpp23zig/.zig-cache --global-cache-dir $HOME/.cache/zig --name temp --zig-lib-dir $HOME/.local/share/zig/lib/ --listen=-
Build Summary: 0/3 steps succeeded; 1 failed
install transitive failure
└─ install temp transitive failure
└─ zig build-exe temp Debug native 1 errors
error: the following build command failed with exit code 1:
/path/to/cpp23zig/.zig-cache/o/3cf36c1dace78d0f16a247269e0a8d09/build $HOME/.local/share/zig/zig $HOME/.local/share/zig/lib /path/to/cpp23zig /path/to/cpp23zig/.zig-cache $HOME/.cache/zig --seed 0x375f42b8 -Z1f745c60c89700d7

I haven’t used C++23 modules personally, but it looks like LLVM’s libc++ currently requires you to build your own precompiled module files in order to use the std modules. You may be able to do that with the Zig build system and zig c++ following something like this approach, but it doesn’t look like Zig distributes the C++23 std module files with its libc++. There is currently an open issue related to modules.

2 Likes

std::println appeared in c++23 so you should be able to use it without workaroud. This code compiles for me with your build.zig script.

#include <print>

int main() {
    int answer { 42 };
    std::println("The answer to life, the universe, and everything is {}.", answer);
    return 0;
}

I would assume you ment to say workaround to use it in c++20.
In which case problem arises from poor support of c++ modules. But nothing stops you from downgrading workaround to use regular #includes

Here is working example with c++20:

src/print_workaround.h:

#pragma once

#include <format>
#include <iostream>

namespace std
{
	template <typename... Args>
	void print(const format_string<Args...> format, Args&&... args)
	{
		std::format_to(std::ostreambuf_iterator<char>(std::cout), format, std::forward<Args>(args)...);
	}
	
	template <typename... Args>
	void println(const format_string<Args...> format, Args&&... args)
	{
		print(format, std::forward<Args>(args)...);
		std::cout << '\n';
	}

	template <typename... Args>
	void print(const wformat_string<Args...> format, Args&&... args)
	{
		std::format_to(std::ostreambuf_iterator<wchar_t>(std::wcout), format, std::forward<Args>(args)...);
	}

	template <typename... Args>
	void println(const wformat_string<Args...> format, Args&&... args)
	{
		print(format, std::forward<Args>(args)...);
		std::wcout << L'\n';
	}
}

build.zig:

const std = @import("std");

const CPP_ROOT_DIR = "./src/";

const CPP_FLAGS = &.{
    "-Wall",
    "-Werror",
    "-O2",
    "-std=c++20", // c++23 -> c++20
};

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "temp",
        .target = target,
        .optimize = optimize,
    });

    exe.addCSourceFiles(.{
        .root = b.path(CPP_ROOT_DIR),
        .files = &.{
            "main.cpp",
        },
        .flags = CPP_FLAGS,
    });

    exe.linkLibCpp();

    b.installArtifact(exe);
}

main.cpp:

#include "print_workaround.h"

int main() {
    int answer { 42 };
    std::println("The answer to life, the universe, and everything is {}.", answer);
    return 0;
}
2 Likes

Thanks @permutationlock ! I’ll keep an eye on that issue.


@AndrewKraevskii - The workaround link is from a book that I’m reading - “Beginning C++23: From Beginner to Pro Seventh Edition” by Ivor Horton and Peter Van Weert.

The workaround link is quite old and may not reflect the current state of things. More recent resources for this are tough to come across, thats why I used the workaround. I am indeed looking to use zig build system with C++23. Apologies if my post caused any confusion.

I’m able to compile your code having #include <print>, but I’m looking to import std as its much easier to do than adding a bunch of #includes.

I referred to the links you provided and got it working with cmake. Thanks a lot !


Importing the std module

Using clang

The CMakeLists.txt file looks like:

cmake_minimum_required(VERSION 3.31)

# https://libcxx.llvm.org/Modules.html
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "0e5b6991-d74f-4b3d-a41c-cf096e0b2508")
set(CMAKE_CXX_MODULE_STD ON)
set(CXXFLAGS "-stdlib=libc++")
set(CMAKE_CXX_FLAGS "${CXXFLAGS}")
set(CMAKE_CXX_COMPILER clang++)

project("example"
  LANGUAGES CXX
)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
# Currently CMake requires extensions enabled when using import std.
# https://gitlab.kitware.com/cmake/cmake/-/issues/25916
# https://gitlab.kitware.com/cmake/cmake/-/issues/25539
set(CMAKE_CXX_EXTENSIONS ON)

add_executable(main)
target_sources(main
  PRIVATE
  src/main.cpp
)

and src/main.cpp:

import std;

int main() {
    int answer { 42 };
    std::println("The answer to life, the universe, and everything is {}.", answer);
    return 0;
}

which can be executed with:

  • cmake -G Ninja -S . -B build

  • ninja -C build

  • ./build/main

1 Like