GPMueller
GPMueller

Reputation: 3149

Clang: reliably detect supported C++ standard from command line or Python

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?

Upvotes: 6

Views: 3836

Answers (1)

Mike Kinghan
Mike Kinghan

Reputation: 61515

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
>>>

Upvotes: 4

Related Questions