6

In a Python script, I am trying to determine the highest C++ standard supported by the installed Clang.

One problem is that I cannot rely on the output of clang --version to always be the same - the best example is AppleClang on OSX. Trying to compile a hello world .cpp file with test-flags like -std=c++11, -std=c++14, ... does not seem the most robust approach and would require the creation of temporary files.

Is there any command one could run to test if a certain dialect is available without actually compiling anything?

GPMueller
  • 2,881
  • 2
  • 27
  • 36
  • 1
    I would just try to compile the features I was interested in, it would be most robust... If clang works like gcc, you can feed it source from *stdin* too and avoid maybe tempfiles. – Prof. Falken Feb 27 '18 at 15:25
  • 1
    @Prof.Falken Yes. Isn't that how Autoconf, `./configure`, does it? – simon Feb 27 '18 at 15:26
  • Not all features of a given standard are supported. I think it depends on a version. Here is the [C++ Support in Clang](https://clang.llvm.org/cxx_status.html) documentation and the [C++ compiler support](http://en.cppreference.com/w/cpp/compiler_support) reference. – Ron Feb 27 '18 at 15:26
  • @gurka, it is. Now, Autoconf is a *beast* of a thing and I understand if someone wants to avoid it in some situations. – Prof. Falken Feb 27 '18 at 15:27
  • 1
    "without actually compiling anything?" - not without a database of release versions and capabilities. The way cmake and autoconf do it is to actually compile code snippets and check the return code of the process. – Richard Hodges Feb 27 '18 at 15:27
  • "compile the features I was interested in" You mean by creating a temporary file containing C++ code using the relevant features? I'm actually more interested in the supported dialect version, not specific features. – GPMueller Feb 27 '18 at 15:28
  • "compile code snippets and check the return code of the process" checking if `clang -std=c++17 test.cpp` runs would work, even with just a main inside the test.cpp, but I don't like the idea of the script creating files... – GPMueller Feb 27 '18 at 15:31
  • You don't need to create files. You can use `/dev/stdin` or `--` to read from stdin. – arrowd Feb 27 '18 at 15:48
  • @arrowd unless I'm missing some Python knowledge, `/dev/stdin` won't be cross-platform. I'm afraid I don't know what you mean with `--`, could you elaborate? – GPMueller Feb 27 '18 at 16:46
  • Ah, `--` only works for output, alas. Well, then I doubt there is a way. – arrowd Feb 27 '18 at 16:50

1 Answers1

4

Is there any command one could run to test if a certain dialect is available without actually compiling anything?

Yes. You can ask the compiler just to preprocess an empty file. It will do that without complaint:

$ clang++ --version
clang version 4.0.1-6 (tags/RELEASE_401/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

$ echo '' | clang++ -x c++ -E -
# 1 "<stdin>"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "<stdin>" 2

$ echo $?
0

You can then incidentally add a -std option. If the compiler supports it:

$ echo '' | clang++ -std=c++98 -x c++ -E -
# 1 "<stdin>"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 326 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "<stdin>" 2

$ echo $?
0

still no complaint. But if not:

$ echo '' | clang++ -std=c++17 -x c++ -E -
error: invalid value 'c++17' in '-std=c++17'
$ echo $?
1

In a python script, you can conveniently supply an empty input file in the form of an empty string to an invocation of subprocess.run that executes the compiler probing command, and at the same time swallows the unwanted stdout. You'd iterate such invocations over a chronologically sorted list of -std-values to find the latest supported. It would be prudent not simply to test the return code but also to capture the stderr and, in case of failure, parse it for the right sort of diagnostic, in case the command has failed for some surprise reason.

Here's a shot that serves for GCC as well as clang:

$ cat std_max.py
#!/usr/bin/python3
import subprocess

standards = ['98','03','11','14','17']
gpp_barf_pattern = "error: unrecognized command line option ‘-std=c++{0}’"
clangpp_barf_pattern = "error: invalid value 'c++{0}'"

def has_standard(compiler, std_year, barf_pattern):
    std_opt = '-std=c++' + std_year
    try:
        subprocess.run([compiler,std_opt,'-x','c++','-E','-'],\
            check=True,input=b'',stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    except subprocess.CalledProcessError as e:
        barf = barf_pattern.format(std_year)
        strerr = e.stderr.decode('utf8','strict')
        if barf in strerr:
            return False
        raise
    return True

def get_std_max(compiler,standards,barf_pattern):
    max_std = standards[0] if len(standards) else ''
    for std_year in standards:
        if not has_standard(compiler,std_year,barf_pattern):
            break
        max_std = 'c++' + std_year
    return max_std

which will tell me, correctly:

$ python3
Python 3.6.3 (default, Oct  3 2017, 21:45:48) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from std_max import *
>>> get_std_max('clang++',standards,clangpp_barf_pattern)
'c++14'
>>> get_std_max('g++',standards,gpp_barf_pattern)
'c++17'
>>>

No C++20 yet:

>>> has_standard('g++','20',gpp_barf_pattern)
False
>>>
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182