Clang 10 in a Docker container for C++17 and C++20 development

Updated 19 April 2020

If you want to try the latest stable version of Clang in a Docker container, you are in the right place. Running Clang in a container has the advantage that it is light on resources and won’t mess with your underlying OS. The last point is especially important if your host operating system is macOS, on which it is a really bad idea to directly install a binary Clang other than the one that comes with Xcode. I’ve tested the approach presented in this article on Windows 10, macOS Mojave and Ubuntu Linux.

I assume that you have Docker installed on your machine, if not go to the Docker website and install it. After the installation, open a Terminal or, if you are on Windows, a PowerShell window and check if Docker was properly installed with:

1 docker version

This is a snippet of what I see on a macOS machine:

1 ~ $ docker version 2 Client: Docker Engine - Community 3 Version: 19.03.8 4 API version: 1.40 5 Go version: go1.12.17 6 Git commit: afacb8b 7 Built: Wed Mar 11 01:21:11 2020 8 OS/Arch: darwin/amd64 9 Experimental: false 10 11 Server: Docker Engine - Community 12 Engine: 13 Version: 19.03.8 14 API version: 1.40 (minimum version 1.12) 15 Go version: go1.12.17 16 Git commit: afacb8b 17 Built: Wed Mar 11 01:29:16 2020 18 OS/Arch: linux/amd64 19 Experimental: false 20 21 ...

Next, we are going to use a Dockerfile that will prepare an Ubuntu image with the latest Clang:

1 # Check http://releases.llvm.org/download.html#10.0.0 for the latest available binaries 2 FROM ubuntu:18.04 3 4 # Make sure the image is updated, install some prerequisites, 5 # Download the latest version of Clang (official binary) for Ubuntu 6 # Extract the archive and add Clang to the PATH 7 RUN apt-get update && apt-get install -y \ 8 xz-utils \ 9 build-essential \ 10 curl \ 11 && rm -rf /var/lib/apt/lists/* \ 12 && curl -SL https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz \ 13 | tar -xJC . && \ 14 mv clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04 clang_10.0.0 && \ 15 echo 'export PATH=/clang_10.0.0/bin:$PATH' >> ~/.bashrc && \ 16 echo 'export LD_LIBRARY_PATH=/clang_10.0.0/lib:$LD_LIBRARY_PATH' >> ~/.bashrc 17 18 # Start from a Bash prompt 19 CMD [ "/bin/bash" ]

You can get the last version of the above Dockerfile from my GitHub (Hint: use the Download button if you don’t have a Git client on your machine):

1 git clone https://github.com/sol-prog/Clang-in-Docker.git 2 cd Clang-in-Docker

In order to build an up do date image, you can use (make sure you are in the same folder as the Dockerfile):

1 docker build -t ubuntu_clang_image .

the above will create a new image named ubuntu_clang_image. Once the process is finished, you can check the list of available images with:

1 docker image ls

Now, we need to create a container from ubuntu_clang_image. First, we’ll create a folder that will be shared between our host OS and the Docker container, this will let us use a our preferred C++ text editor (or IDE) from our host:

1 cd ~ 2 mkdir clang_tests

If your host OS is Windows 7, 8 or 10, you need to do an extra step. Make sure that you shared your drives with Docker (this will let you share a local Windows folder with a Docker container). For example, if you want to share drive C, open the Docker’s Settings panel check the drive and press Apply:

Next, we’ll create a container named ubuntu_clang from the above image:

1 docker run -it -v $PWD/clang_tests:/clang_tests --name ubuntu_clang ubuntu_clang_image /bin/bash

Please note that you need to run the above command only once!

At this point, you should be at a Bash prompt in your container, e.g. this is what I see on my machine:

1 ~ $ docker run -it -v $PWD/clang_tests:/clang_tests --name ubuntu_clang ubuntu_clang_image /bin/bash 2 root@16474c8d08cd:/#

Check the version of Clang with:

1 clang --version

you should see something like this:

1 clang version 10.0.0 2 Target: x86_64-unknown-linux-gnu 3 Thread model: posix 4 InstalledDir: /clang_10.0.0/bin 5 root@16474c8d08cd:/#

Now, from your host machine, go to clang_tests and create a new file named if_test.cpp, with the next C++ code:

1 // if block with init-statement, the example is a bit silly, 2 // but you can use it to check if your compiler supports C++17: 3 #include <iostream> 4 5 int main () { 6 // if block with init-statement: 7 if ( int a = 5 ; a < 8 ) { 8 std :: cout << "Local variable a is < 8

" ; 9 } else { 10 std :: cout << "Local variable a is >= 8

" ; 11 } 12 return 0 ; 13 }

The file should be visible from the container too, cd to clang_tests and check what files are present, this is what I see:

1 root@16474c8d08cd:/# cd clang_tests/ 2 root@16474c8d08cd:/clang_tests# ls 3 if_test.cpp 4 root@16474c8d08cd:/clang_tests#

You can compile and run the above C++ program with:

1 clang++ -std=c++17 -stdlib=libc++ -Wall -pedantic if_test.cpp -o if_test 2 ./if_test

This is what I see on my container:

1 root@16474c8d08cd:/clang_tests# clang++ -std = c++17 -stdlib = libc++ -Wall -pedantic if_test.cpp -o if_test 2 root@16474c8d08cd:/clang_tests# ./if_test 3 Local variable a is < 8 4 root@16474c8d08cd:/clang_tests#

Next, let’s try to compile a program that uses the C++17 Filesystem:

1 // C++17 filesystem test. 2 // you can build the code with: 3 // clang++ -std=c++17 -stdlib=libc++ -Wall -pedantic fs_test.cpp -o fs_test 4 5 #include <iostream> 6 #include <filesystem> 7 8 int main () { 9 for ( auto & file : std :: filesystem :: recursive_directory_iterator ( "./" )) { 10 std :: cout << file . path () << '

' ; 11 } 12 }

Save the above file as fs_test.cpp and compile it with:

1 clang++ -std=c++17 -stdlib=libc++ -Wall -pedantic fs_test.cpp -o fs_test

This is what I see on my container if I run the above code (you should see a list of files that are present in the folder where you have the executable):

1 root@16474c8d08cd:/clang_tests# clang++ -std = c++17 -stdlib = libc++ -Wall -pedantic fs_test.cpp -o fs_test 2 root@16474c8d08cd:/clang_tests# ./fs_test 3 "./fs_test.cpp" 4 "./fs_test" 5 "./if_test" 6 "./if_test.cpp" 7 root@16474c8d08cd:/clang_tests#

Finally, let’s test if we can use C++17 std::optional:

1 // C++17 optional test. 2 // You can compile the code with: 3 // clang++ -std=c++17 -stdlib=libc++ -Wall -pedantic optional_test.cpp -o optional_test 4 #include <iostream> 5 #include <optional> 6 7 void show_optional_value ( const std :: optional < int > & o ) { 8 // Print the optional has a value if it is not empty 9 if ( o ) { 10 std :: cout << "Optional value is: " << * o << '

' ; 11 } else { 12 std :: cout << "Optional is empty

" ; 13 } 14 } 15 int main () { 16 // Create an empty optional 17 std :: optional < int > o1 ; 18 19 // Show the optional value 20 show_optional_value ( o1 ); 21 22 // Store a value in the above optional variable 23 o1 = - 33 ; 24 25 // Show the optional value 26 show_optional_value ( o1 ); 27 }

This is what I see if I build and run the above code:

1 root@16474c8d08cd:/clang_tests# clang++ -std = c++17 -stdlib = libc++ -Wall -pedantic optional_test.cpp -o optional_test 2 root@16474c8d08cd:/clang_tests# ./optional_test 3 Optional is empty 4 Optional value is: -33 5 root@16474c8d08cd:/clang_tests#

Clang 10 has partial support for the new C++20 standard, you can use std=c++20 in order to enforce the C++20 standard. Here is an example of using the new C++20 std::span:

1 // C++20 span test 2 // you can build the code with: 3 // clang++ -std=c++2a -stdlib=libc++ -Wall -Wextra -pedantic span_test.cpp -o span_test 4 5 #include <iostream> 6 #include <vector> 7 #include <span> 8 9 void print_content ( std :: span < int > container ) { 10 for ( const auto & e : container ) { 11 std :: cout << e << ' ' ; 12 } 13 std :: cout << '

' ; 14 } 15 16 int main () { 17 int a []{ 23 , 45 , 67 , 89 }; 18 print_content ( a ); 19 20 std :: vector < int > v { 1 , 2 , 3 , 4 , 5 }; 21 print_content ( v ); 22 }

This is what I see if I build and run the above code:

1 root@16474c8d08cd:/clang_tests# clang++ -std = c++20 -stdlib = libc++ -Wall -Wextra -pedantic span_test.cpp -o span_test 2 root@16474c8d08cd:/clang_tests# ./span_test 3 23 45 67 89 4 1 2 3 4 5 5 root@16474c8d08cd:/clang_tests#

If you want to leave the container, just write exit and you should be at your default shell prompt, e.g.:

1 root@16474c8d08cd:/clang_tests# exit 2 exit 3 ~ $

Next time when you want to start the container, you can do it with:

1 docker start -ia ubuntu_clang

If you don’t remember the name of your Docker container, use the next command to list all available containers:

1 docker container ls -a

If you are interested to learn more about modern C++ I would recommend reading A tour of C++ by Bjarne Stroustrup.

or Effective Modern C++ by Scott Meyers.