From 0e28d366bccbbf9fd36918bae2908a3a4c74bf8c Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Wed, 2 Dec 2015 20:00:28 +0100 Subject: [PATCH] C++, add support for pointers to member (function) See sphinx-doc/sphinx#2146 --- CHANGES | 1 + sphinx/domains/cpp.py | 295 +++++++++++++++++++++++++++++++-------- tests/test_domain_cpp.py | 161 ++++++++++++--------- 3 files changed, 332 insertions(+), 125 deletions(-) diff --git a/CHANGES b/CHANGES index 4566566c7..1a8217c44 100644 --- a/CHANGES +++ b/CHANGES @@ -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 ---------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 00bfb8663..8cbf96e1d 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -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': diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 78ac9c2c8..ba96945fd 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -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 &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 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 # 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::type', None, "1A") check('enum', 'A : unsigned int', None, "1A") @@ -250,29 +343,6 @@ def test_type_definitions(): check('enumerator', 'A = std::numeric_limits::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", None, "IE1AI1TE", output="template<> A") @@ -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: