C++, add support for pointers to member (function)

See sphinx-doc/sphinx#2146
This commit is contained in:
Jakob Lykke Andersen 2015-12-02 20:00:28 +01:00
parent e800fef3d1
commit 0e28d366bc
3 changed files with 332 additions and 125 deletions

View File

@ -18,6 +18,7 @@ Features added
* Intersphinx: Added support for fetching Intersphinx inventories with URLs
using HTTP basic auth
* C++, added support for template parameter in function info field lists.
* C++, added support for pointers to member (function).
Bugs fixed
----------

View File

@ -151,7 +151,7 @@ from sphinx.util.docfields import Field, GroupedField
| "& attribute-specifier-seq[opt]
| "&&" attribute-specifier-seq[opt]
| "::"[opt] nested-name-specifier "*" attribute-specifier-seq[opt]
cv-qualifier-seq[opt] # TOOD: not implemented
cv-qualifier-seq[opt]
# function_object must use a parameters-and-qualifiers, the others may
# use it (e.g., function poitners)
parameters-and-qualifiers ->
@ -1402,9 +1402,14 @@ class ASTDeclaratorPtr(ASTBase):
def __unicode__(self):
res = ['*']
if self.volatile:
res.append('volatile ')
res.append('volatile')
if self.const:
res.append('const ')
if self.volatile:
res.append(' ')
res.append('const')
if self.const or self.volatile:
if self.next.require_space_after_declSpecs:
res.append(' ')
res.append(text_type(self.next))
return u''.join(res)
@ -1459,6 +1464,18 @@ class ASTDeclaratorPtr(ASTBase):
def describe_signature(self, signode, mode, env, symbol):
_verify_description_mode(mode)
signode += nodes.Text("*")
def _add_anno(signode, text):
signode += addnodes.desc_annotation(text, text)
if self.volatile:
_add_anno(signode, 'volatile')
if self.const:
if self.volatile:
signode += nodes.Text(' ')
_add_anno(signode, 'const')
if self.const or self.volatile:
if self.next.require_space_after_declSpecs:
signode += nodes.Text(' ')
self.next.describe_signature(signode, mode, env, symbol)
@ -1571,6 +1588,94 @@ class ASTDeclaratorParamPack(ASTBase):
self.next.describe_signature(signode, mode, env, symbol)
class ASTDeclaratorMemPtr(ASTBase):
def __init__(self, className, const, volatile, next):
assert className
assert next
self.className = className
self.const = const
self.volatile = volatile
self.next = next
@property
def name(self):
return self.next.name
def require_space_after_declSpecs(self):
return True
def __unicode__(self):
res = []
res.append(text_type(self.className))
res.append('::*')
if self.volatile:
res.append(' volatile')
if self.const:
res.append(' const')
if self.next.require_space_after_declSpecs():
res.append(' ')
res.append(text_type(self.next))
return ''.join(res)
# Id v1 ------------------------------------------------------------------
def get_modifiers_id_v1(self):
raise NoOldIdError()
def get_param_id_v1(self): # only the parameters (if any)
raise NoOldIdError()
def get_ptr_suffix_id_v1(self):
raise NoOldIdError()
# Id v2 ------------------------------------------------------------------
def get_modifiers_id_v2(self):
return self.next.get_modifiers_id_v2()
def get_param_id_v2(self): # only the parameters (if any)
return self.next.get_param_id_v2()
def get_ptr_suffix_id_v2(self):
raise NotImplementedError()
return self.next.get_ptr_suffix_id_v2() + u'Dp'
def get_type_id_v2(self, returnTypeId):
# ReturnType name::* next, so we are part of the return type of next
nextReturnTypeId = ''
if self.volatile:
nextReturnTypeId += 'V'
if self.const:
nextReturnTypeId += 'K'
nextReturnTypeId += 'M'
nextReturnTypeId += self.className.get_id_v2()
nextReturnTypeId += returnTypeId
return self.next.get_type_id_v2(nextReturnTypeId)
# ------------------------------------------------------------------------
def is_function_type(self):
return self.next.is_function_type()
def describe_signature(self, signode, mode, env, symbol):
_verify_description_mode(mode)
self.className.describe_signature(signode, mode, env, symbol)
signode += nodes.Text('::*')
def _add_anno(signode, text):
signode += addnodes.desc_annotation(text, text)
if self.volatile:
_add_anno(signode, 'volatile')
if self.const:
if self.volatile:
signode += nodes.Text(' ')
_add_anno(signode, 'const')
if self.next.require_space_after_declSpecs():
if self.volatile or self.const:
signode += nodes.Text(' ')
self.next.describe_signature(signode, mode, env, symbol)
class ASTDeclaratorParen(ASTBase):
def __init__(self, inner, next):
assert inner
@ -2541,6 +2646,30 @@ class DefinitionParser(object):
self.warnEnv = warnEnv
def _make_multi_error(self, errors, header):
if len(errors) == 1:
return DefinitionError(header + '\n' + errors[0][0].description)
result = [header, '\n']
for e in errors:
if len(e[1]) > 0:
ident = ' '
result.append(e[1])
result.append(':\n')
for line in e[0].description.split('\n'):
if len(line) == 0:
continue
result.append(ident)
result.append(line)
result.append('\n')
else:
result.append(e[0].description)
return DefinitionError(''.join(result))
def status(self, msg):
# for debugging
indicator = '-' * self.pos + '^'
print("%s\n%s\n%s" % (msg, self.definition, indicator))
def fail(self, msg):
indicator = '-' * self.pos + '^'
raise DefinitionError(
@ -2672,6 +2801,7 @@ class DefinitionParser(object):
self.skip_ws()
if not self.skip_string('<'):
return None
prevErrors = []
templateArgs = []
while 1:
pos = self.pos
@ -2688,7 +2818,7 @@ class DefinitionParser(object):
self.fail('Expected ">" or "," in template argument list.')
templateArgs.append(type)
except DefinitionError as e:
errorType = e.description
prevErrors.append((e, "If type argument"))
self.pos = pos
try:
value = self._parse_expression(end=[',', '>'])
@ -2701,18 +2831,16 @@ class DefinitionParser(object):
self.fail('Expected ">" or "," in template argument list.')
templateArgs.append(ASTTemplateArgConstant(value))
except DefinitionError as e:
errorExpr = e.description
msg = "Error in parsing template argument list. " \
"Error if type argument:\n%s\n" \
"Error if non-type argument:\n%s" \
% (errorType, errorExpr)
self.fail(msg)
self.pos = pos
prevErrors.append((e, "If non-type argument"))
header = "Error in parsing template argument list."
raise self._make_multi_error(prevErrors, header)
if parsedEnd:
assert not parsedComma
break
return ASTTemplateArgs(templateArgs)
def _parse_nested_name(self):
def _parse_nested_name(self, memberPointer=False):
names = []
self.skip_ws()
@ -2728,6 +2856,8 @@ class DefinitionParser(object):
names.append(op)
else:
if not self.match(_identifier_re):
if memberPointer and len(names) > 0:
break
self.fail("Expected identifier in nested name.")
identifier = self.matched_text
# make sure there isn't a keyword
@ -2740,6 +2870,8 @@ class DefinitionParser(object):
self.skip_ws()
if not self.skip_string('::'):
if memberPointer:
self.fail("Expected '::' in pointer to member (function).")
break
return ASTNestedName(names, rooted)
@ -2808,7 +2940,7 @@ class DefinitionParser(object):
'parameters_and_qualifiers.')
break
if paramMode == 'function':
arg = self._parse_type_with_init(outer=None, named='maybe')
arg = self._parse_type_with_init(outer=None, named='single')
else:
arg = self._parse_type(named=False)
# TODO: parse default parameters # TODO: didn't we just do that?
@ -2824,7 +2956,10 @@ class DefinitionParser(object):
'Expecting "," or ")" in parameters_and_qualifiers, '
'got "%s".' % self.current_char)
if paramMode != 'function':
# TODO: why did we have this bail-out?
# does it hurt to parse the extra stuff?
# it's needed for pointer to member functions
if paramMode != 'function' and False:
return ASTParametersQualifiers(
args, None, None, None, None, None, None, None)
@ -2965,26 +3100,27 @@ class DefinitionParser(object):
def _parse_declarator_name_param_qual(self, named, paramMode, typed):
# now we should parse the name, and then suffixes
if named == 'maybe':
pos = self.pos
try:
declId = self._parse_nested_name()
except DefinitionError:
self.pos = pos
declId = None
elif named == 'single':
if self.match(_identifier_re):
identifier = ASTIdentifier(self.matched_text)
nne = ASTNestedNameElement(identifier, None)
declId = ASTNestedName([nne], rooted=False)
# if it's a member pointer, we may have '::', which should be an error
self.skip_ws()
if self.current_char == ':':
self.fail("Unexpected ':' after identifier.")
else:
declId = None
elif named:
declId = self._parse_nested_name()
else:
declId = None
self.skip_ws()
if typed and declId:
if self.skip_string("*"):
self.fail("Member pointers not implemented.")
arrayOps = []
while 1:
self.skip_ws()
@ -3005,6 +3141,7 @@ class DefinitionParser(object):
if paramMode not in ('type', 'function', 'operatorCast'):
raise Exception(
"Internal error, unknown paramMode '%s'." % paramMode)
prevErrors = []
self.skip_ws()
if typed and self.skip_string('*'):
self.skip_ws()
@ -3023,13 +3160,39 @@ class DefinitionParser(object):
next = self._parse_declerator(named, paramMode, typed)
return ASTDeclaratorPtr(next=next, volatile=volatile, const=const)
# TODO: shouldn't we parse an R-value ref here first?
elif typed and self.skip_string("&"):
if typed and self.skip_string("&"):
next = self._parse_declerator(named, paramMode, typed)
return ASTDeclaratorRef(next=next)
elif typed and self.skip_string("..."):
if typed and self.skip_string("..."):
next = self._parse_declerator(named, paramMode, False)
return ASTDeclaratorParamPack(next=next)
elif typed and self.current_char == '(': # note: peeking, not skipping
if typed: # pointer to member
pos = self.pos
try:
name = self._parse_nested_name(memberPointer=True)
self.skip_ws()
if not self.skip_string('*'):
self.fail("Expected '*' in pointer to member declarator.")
self.skip_ws()
except DefinitionError as e:
self.pos = pos
prevErrors.append((e, "If pointer to member declarator"))
else:
volatile = False
const = False
while 1:
if not volatile:
volatile = self.skip_word_and_ws('volatile')
if volatile:
continue
if not const:
const = self.skip_word_and_ws('const')
if const:
continue
break
next = self._parse_declerator(named, paramMode, typed)
return ASTDeclaratorMemPtr(name, const, volatile, next=next)
if typed and self.current_char == '(': # note: peeking, not skipping
if paramMode == "operatorCast":
# TODO: we should be able to parse cast operators which return
# function pointers. For now, just hax it and ignore.
@ -3037,14 +3200,15 @@ class DefinitionParser(object):
paramQual=None)
# maybe this is the beginning of params and quals,try that first,
# otherwise assume it's noptr->declarator > ( ptr-declarator )
startPos = self.pos
pos = self.pos
try:
# assume this is params and quals
res = self._parse_declarator_name_param_qual(named, paramMode,
typed)
return res
except DefinitionError as exParamQual:
self.pos = startPos
prevErrors.append((exParamQual, "If declId, parameters, and qualifiers"))
self.pos = pos
try:
assert self.current_char == '('
self.skip_string('(')
@ -3059,13 +3223,18 @@ class DefinitionParser(object):
typed=typed)
return ASTDeclaratorParen(inner=inner, next=next)
except DefinitionError as exNoPtrParen:
raise DefinitionError(
"If declId, parameters, and qualifiers {\n%s\n"
"} else If parenthesis in noptr-declarator {\n%s\n}"
% (exParamQual, exNoPtrParen))
else:
return self._parse_declarator_name_param_qual(named, paramMode,
typed)
self.pos = pos
prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
header = "Error in declarator"
raise self._make_multi_error(prevErrors, header)
pos = self.pos
try:
return self._parse_declarator_name_param_qual(named, paramMode, typed)
except DefinitionError as e:
self.pos = pos
prevErrors.append((e, "If declarator-id"))
header = "Error in declarator or parameters and qualifiers"
raise self._make_multi_error(prevErrors, header)
def _parse_initializer(self, outer=None):
self.skip_ws()
@ -3102,6 +3271,7 @@ class DefinitionParser(object):
# We allow type objects to just be a name.
# Some functions don't have normal return types: constructors,
# destrutors, cast operators
prevErrors = []
startPos = self.pos
# first try without the type
try:
@ -3110,37 +3280,49 @@ class DefinitionParser(object):
typed=False)
self.assert_end()
except DefinitionError as exUntyped:
if outer == 'type':
desc = "If just a name"
elif outer == 'function':
desc = "If the function has no return type"
else:
assert False
prevErrors.append((exUntyped, desc))
self.pos = startPos
try:
declSpecs = self._parse_decl_specs(outer=outer)
decl = self._parse_declerator(named=True, paramMode=outer)
except DefinitionError as exTyped:
self.pos = startPos
if outer == 'type':
desc = "If typedef-like declaration"
elif outer == 'function':
desc = "If the function has a return type"
else:
assert False
prevErrors.append((exTyped, desc))
# Retain the else branch for easier debugging.
# TODO: it would be nice to save the previous stacktrace
# and output it here.
if True:
if outer == 'type':
desc = ('Type must be either just a name or a '
'typedef-like declaration.\n'
'Just a name error: %s\n'
'Typedef-like expression error: %s')
header = "Type must be either just a name or a "
header += "typedef-like declaration."
elif outer == 'function':
desc = ('Error when parsing function declaration:\n'
'If no return type {\n%s\n'
'} else if return type {\n%s\n}')
header = "Error when parsing function declaration."
else:
assert False
raise DefinitionError(
desc % (exUntyped.description, exTyped.description))
raise self._make_multi_error(prevErrors, header)
else:
# For testing purposes.
# do it again to get the proper traceback (how do you
# relieable save a traceback when an exception is
# constructed?)
pass
self.pos = startPos
declSpecs = self._parse_decl_specs(outer=outer, typed=False)
typed = True
declSpecs = self._parse_decl_specs(outer=outer, typed=typed)
decl = self._parse_declerator(named=True, paramMode=outer,
typed=False)
typed=typed)
else:
paramMode = 'type'
if outer == 'member': # i.e., member
@ -3226,7 +3408,7 @@ class DefinitionParser(object):
if not self.skip_string("<"):
self.fail("Expected '<' after 'template'")
while 1:
extraError = ''
prevErrors = []
self.skip_ws()
if self.skip_word('template'):
# declare a tenplate template parameter
@ -3272,19 +3454,20 @@ class DefinitionParser(object):
param = self._parse_type_with_init('maybe', 'templateParam')
templateParams.append(ASTTemplateParamNonType(param))
except DefinitionError as e:
prevErrors.append((e, "If non-type template parameter"))
self.pos = pos
extraError = "Error if non-type template parameter: %s"
extraError = extraError % e.description
self.skip_ws()
if self.skip_string('>'):
return ASTTemplateParams(templateParams)
elif self.skip_string(','):
continue
else:
msg = 'Expected "=", ",", or ">" in template parameter list.'
if len(extraError) > 0:
msg += '\n%s' % extraError
self.fail(msg)
header = "Error in template parameter list."
try:
self.fail('Expected "=", ",", or ">".')
except DefinitionError as e:
prevErrors.append((e, ""))
raise self._make_multi_error(prevErrors, header)
def _parse_template_declaration_prefix(self):
templates = []
@ -3347,25 +3530,23 @@ class DefinitionParser(object):
templatePrefix = self._parse_template_declaration_prefix()
if objectType == 'type':
error = None
prevErrors = []
pos = self.pos
try:
if not templatePrefix:
declaration = self._parse_type(named=True, outer='type')
except DefinitionError as e:
error = e.description
prevErrors.append((e, "If typedef-like declaration"))
self.pos = pos
pos = self.pos
try:
if not declaration:
declaration = self._parse_type_using()
except DefinitionError as e:
if error:
msg = "Error if typedef:\n%s\n" \
"Error if type alias or template alias:\n%s" \
% (error, e.description)
raise DefinitionError(msg)
else:
raise e
self.pos = pos
prevErrors.append((e, "If type alias or template alias"))
header = "Error in type declaration."
raise self._make_multi_error(prevErrors, header)
elif objectType == 'member':
declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function':

View File

@ -78,6 +78,27 @@ def check(name, input, idv1output=None, idv2output=None, output=None):
#print ".. %s:: %s" % (name, input)
def test_fundamental_types():
# see http://en.cppreference.com/w/cpp/language/types
for t, id_v2 in cppDomain._id_fundamental_v2.items():
if t == "decltype(auto)":
continue
def makeIdV1():
id = t.replace(" ", "-").replace("long", "l").replace("int", "i")
id = id.replace("bool", "b").replace("char", "c")
id = id.replace("wc_t", "wchar_t").replace("c16_t", "char16_t")
id = id.replace("c32_t", "char32_t")
return "f__%s" % id
def makeIdV2():
id = id_v2
if t == "std::nullptr_t":
id = "NSt9nullptr_tE"
return "1f%s" % id
check("function", "void f(%s arg)" % t, makeIdV1(), makeIdV2())
def test_type_definitions():
check("type", "public bool b", "b", "1b", "bool b")
check("type", "bool A::b", "A::b", "N1A1bE")
@ -107,6 +128,10 @@ def test_type_definitions():
# test name in global scope
check("type", "bool ::B::b", "B::b", "N1B1bE")
check('type', 'A = B', None, '1A')
def test_member_definitions():
check('member', ' const std::string & name = 42',
"name__ssCR", "4name", output='const std::string &name = 42')
check('member', ' const std::string & name', "name__ssCR", "4name",
@ -117,7 +142,10 @@ def test_type_definitions():
"name__std::vector:unsigned-i.l:CR",
"4name", output='const std::vector<unsigned int, long> &name')
check('member', 'module::myclass foo[n]', "foo__module::myclassA", "3foo")
check('member', 'int *const p', 'p__iPC', '1p')
def test_function_definitions():
check('function', 'operator bool() const', "castto-b-operatorC", "NKcvbEv")
check('function', 'A::operator bool() const',
"A::castto-b-operatorC", "NK1AcvbEv")
@ -223,6 +251,8 @@ def test_type_definitions():
"A::f__doubleC", "NK1A1fEd")
check("function", "void f(std::shared_ptr<int(double)> ptr)",
None, "1fNSt10shared_ptrIFidEEE")
check("function", "void f(int *const p)", "f__iPC", "1fPCi")
check("function", "void f(int *volatile const p)", "f__iPVC", "1fPVCi")
# TODO: make tests for functions in a template, e.g., Test<int&&()>
# such that the id generation for function type types is correct.
@ -237,9 +267,72 @@ def test_type_definitions():
check('function', 'void f(enum E e)', 'f__E', '1f1E')
check('function', 'void f(union E e)', 'f__E', '1f1E')
# pointer to member (function)
check('function', 'void f(int C::*)', None, '1fM1Ci')
check('function', 'void f(int C::* p)', None, '1fM1Ci')
check('function', 'void f(int ::C::* p)', None, '1fM1Ci')
check('function', 'void f(int C::* const)', None, '1fKM1Ci')
check('function', 'void f(int C::* const&)', None, '1fRKM1Ci')
check('function', 'void f(int C::* volatile)', None, '1fVM1Ci')
check('function', 'void f(int C::* const volatile)', None, '1fVKM1Ci',
output='void f(int C::* volatile const)')
check('function', 'void f(int C::* volatile const)', None, '1fVKM1Ci')
check('function', 'void f(int (C::*)(float, double))', None, '1fM1CFifdE')
check('function', 'void f(int (C::* p)(float, double))', None, '1fM1CFifdE')
check('function', 'void f(int (::C::* p)(float, double))', None, '1fM1CFifdE')
check('function', 'void f(void (C::*)() const &)', None, '1fM1CKRFvvE')
check('function', 'int C::* f(int, double)', None, '1fid')
check('function', 'void f(int C::* *)', None, '1fPM1Ci')
def test_operators():
check('function', 'void operator new [ ] ()',
"new-array-operator", "nav", output='void operator new[]()')
check('function', 'void operator delete ()',
"delete-operator", "dlv", output='void operator delete()')
check('function', 'operator bool() const',
"castto-b-operatorC", "NKcvbEv", output='operator bool() const')
check('function', 'void operator * ()',
"mul-operator", "mlv", output='void operator*()')
check('function', 'void operator - ()',
"sub-operator", "miv", output='void operator-()')
check('function', 'void operator + ()',
"add-operator", "plv", output='void operator+()')
check('function', 'void operator = ()',
"assign-operator", "aSv", output='void operator=()')
check('function', 'void operator / ()',
"div-operator", "dvv", output='void operator/()')
check('function', 'void operator % ()',
"mod-operator", "rmv", output='void operator%()')
check('function', 'void operator ! ()',
"not-operator", "ntv", output='void operator!()')
check('function', 'void operator "" _udl()',
None, 'li4_udlv', output='void operator""_udl()')
def test_class_definitions():
check('class', 'public A', "A", "1A", output='A')
check('class', 'private A', "A", "1A")
check('class', 'A final', 'A', '1A')
# test bases
check('class', 'A', "A", "1A")
check('class', 'A::B::C', "A::B::C", "N1A1B1CE")
check('class', 'A : B', "A", "1A")
check('class', 'A : private B', "A", "1A", output='A : B')
check('class', 'A : public B', "A", "1A")
check('class', 'A : B, C', "A", "1A")
check('class', 'A : B, protected C, D', "A", "1A")
check('class', 'A : virtual private B', 'A', '1A', output='A : virtual B')
check('class', 'A : B, virtual C', 'A', '1A')
check('class', 'A : public virtual B', 'A', '1A')
check('class', 'A : B, C...', 'A', '1A')
check('class', 'A : B..., C', 'A', '1A')
def test_enum_definitions():
check('enum', 'A', None, "1A")
check('enum', 'A : std::underlying_type<B>::type', None, "1A")
check('enum', 'A : unsigned int', None, "1A")
@ -250,29 +343,6 @@ def test_type_definitions():
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()',
None, "1A")
check('type', 'A = B', None, '1A')
def test_fundamental_types():
# see http://en.cppreference.com/w/cpp/language/types
for t, id_v2 in cppDomain._id_fundamental_v2.items():
if t == "decltype(auto)":
continue
def makeIdV1():
id = t.replace(" ", "-").replace("long", "l").replace("int", "i")
id = id.replace("bool", "b").replace("char", "c")
id = id.replace("wc_t", "wchar_t").replace("c16_t", "char16_t")
id = id.replace("c32_t", "char32_t")
return "f__%s" % id
def makeIdV2():
id = id_v2
if t == "std::nullptr_t":
id = "NSt9nullptr_tE"
return "1f%s" % id
check("function", "void f(%s arg)" % t, makeIdV1(), makeIdV2())
def test_templates():
check('class', "A<T>", None, "IE1AI1TE", output="template<> A<T>")
@ -318,51 +388,6 @@ def test_templates():
"RK18c_string_view_baseIK4Char6TraitsE")
def test_class():
check('class', 'A final', 'A', '1A')
def test_bases():
check('class', 'A', "A", "1A")
check('class', 'A::B::C', "A::B::C", "N1A1B1CE")
check('class', 'A : B', "A", "1A")
check('class', 'A : private B', "A", "1A", output='A : B')
check('class', 'A : public B', "A", "1A")
check('class', 'A : B, C', "A", "1A")
check('class', 'A : B, protected C, D', "A", "1A")
check('class', 'A : virtual private B', 'A', '1A', output='A : virtual B')
check('class', 'A : B, virtual C', 'A', '1A')
check('class', 'A : public virtual B', 'A', '1A')
check('class', 'A : B, C...', 'A', '1A')
check('class', 'A : B..., C', 'A', '1A')
def test_operators():
check('function', 'void operator new [ ] ()',
"new-array-operator", "nav", output='void operator new[]()')
check('function', 'void operator delete ()',
"delete-operator", "dlv", output='void operator delete()')
check('function', 'operator bool() const',
"castto-b-operatorC", "NKcvbEv", output='operator bool() const')
check('function', 'void operator * ()',
"mul-operator", "mlv", output='void operator*()')
check('function', 'void operator - ()',
"sub-operator", "miv", output='void operator-()')
check('function', 'void operator + ()',
"add-operator", "plv", output='void operator+()')
check('function', 'void operator = ()',
"assign-operator", "aSv", output='void operator=()')
check('function', 'void operator / ()',
"div-operator", "dvv", output='void operator/()')
check('function', 'void operator % ()',
"mod-operator", "rmv", output='void operator%()')
check('function', 'void operator ! ()',
"not-operator", "ntv", output='void operator!()')
check('function', 'void operator "" _udl()',
None, 'li4_udlv', output='void operator""_udl()')
#def test_print():
# # used for getting all the ids out for checking
# for a in ids: