简体   繁体   中英

why a conversion function declaration does not require at least one defining-type-specifier

Except in a declaration of a constructor, destructor, or conversion function, at least one defining-type-specifier that is not a cv-qualifier shall appear in a complete type-specifier-seq or a complete decl-specifier-seq .

Constructor is an exception because constructor can be declared like constructor(){} , No defining-type-specifier in this declaration, similarly for destructor.

For conversion function , I can't have a idea that a conversion function does not need a type-specifier when I think in the above quote, defining-type-specifier contains type-specifier , because the sentence defining-type-specifier that is not a cv-qualifier implies that, only type-specifier contains cv-qualifier, Hence I think a conversion function declaration shall contain at least a defining-type-specifier (in less range, it's type-specifier that is a subset of defining-type-specifier), The grammar of a conversion function likes as the following, due to[dcl.fct.def#general-1] :

attribute-specifier-seq(opt) decl-specifier-seq(opt) declarator virt-specifier-seq(opt) function-body

Thereof, its declarator will like as the following:

operator conversion-type-id

conversion-type-id

type-specifier-seq conversion-declarator(opt)

However according to [class.conv.fct] , It says:

A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall be neither a defining-type-specifier nor static.

It means the decl-specifier-seq(opt) can't be a defining-type-specifier nor static.

But, in the type-specifier-seq of a conversion-type-id, It must be a defining-type-specifier.

operator Type(){  // Type is a defining-type-specifier(more exactly,it's a type-specifier)
  return Type{};
}

and you wouldn't define a conversion functions like this:

operator (){ // there's no defining-type-specifier in type-specifier-seq
  //...
}

[dcl.fct#11]

Types shall not be defined in return or parameter types.

This is a restriction for that the defining-type-specifier must appear in a function declaration, Due to

defining-type-specifier consists of:

  • type-specifier
  • class-specifier
  • enum-specifier

class-specifier or enum-specifier can't be used in a decl-specifier-seq of a function delcaration because of the last quote. only type-specifier is permitted.

So, as far as now, what the standard actually wants to say is that, use a more range wording for type-specifier , namely, defining-type-specifier , because you indeed can declare a variable likes struct A{} variable; , there's no class-specifier be contained in type-specifier, hence, as a general rule, the standard use the "wording" defining-type-specifier to cover such cases.

So, why conversion function is an exception in the first rule? If there're any misunderstandings in the above analysis, correct me.

Questions:

  1. why a conversion function is an exception in the first quote?
  2. If a conversion function is an exception, why other functions are not?

A function declaration must have a defining-type-specifier simply means that a function declaration must have the form:

   Type  f();
// ^^^^ defining-type-specifier (in this case, a type-specifier)
// this must be an existing type

and can't be of the form:

f(); // error, no defining-type-specifier

The quoted rule from dcl.fct :

Types shall not be defined in return or parameter types.

has nothing to do with defining-type-specifiers (despite the similar terminology). It simply means that you can't define a type in the declaration of a function.

struct A{} f(); // error, can't define a type in return
void f(struct A{}); // error, can't define a type in parameter

so this is not in conflict with the exceptions quoted at the beginning of your question.

I agree - the paragraph [dcl.type]/3 should instead say something like:

Except in a declaration of a constructor, destructor, or conversion function, or in a lambda-declarator , a complete decl-specifier-seq shall contain at least one defining-type-specifier that is not a cv-qualifier . A complete type-specifier-seq shall contain at least one type-specifier that is not a cv-qualifier .

You're correct that:

  • defining-type-specifier parses a wider set of input token sequences than type-specifier .
  • decl-type-specifier parses an even wider set of input token sequences than defining-type-specifier .
  • const and volatile are valid in parsing any of the three.
  • The syntaxes including class ClassName { ... }; and enum EnumName { ... }; are valid in a defining-type-specifier or a decl-type-specifier but not in a type-specifier .

The C++ grammar uses type-specifier-seq and decl-specifier-seq in many places where the name of a type is expected (plus a few where they're not to name a type). The quoted paragraph [dcl.type]/3 requiring "at least one defining-type-specifier that is not a cv-qualifier " in these sequences is mainly saying that in all of those contexts, variations which don't name a type at all are not permitted: you can't say auto v1 = new const; or static constexpr typedef f(); . Most individual uses have additional restrictions on what sorts of type-specifier can and cannot appear there, but those such rules are in addition to this basic one. In particular, many of them don't allow defining types within the specifier sequence. But since decl-type-specifier is used within simple-declaration as the ordinary way to define classes and enumerations, this rule is not the place for that restriction.

The reason for excluding constructors, destructors, and conversion functions is that although they might not have a decl-type-specifier-seq at all, they might in fact use a decl-type-specifier which does not contain any defining-type-specifier . For example, in

class MyClass {
public:
  explicit MyClass(int);
};

the constructor declaration has a decl-type-specifier whose only specifier is the explicit keyword, which is not a defining-type-specifier .

(However, in looking through, I found another context where the rule should NOT apply: A lambda expression allows an optional decl-specifier-seq after its parameter list, where the only permitted specifiers are mutable and constexpr ; neither is a defining-type-specifier .)

I'm guessing this paragraph version came along with or following a change in the grammar between C++14 and C++17. The initial decl-specifier-seq in a simple-declaration was changed from optional to required. A new grammar symbol nodeclspec-function-declaration was added to cover cases of friend declarations and template-related declarations which declare constructors, destructors, or conversion functions with no initial specifiers and without defining them. Other declarations of constructors, destructors, and conversion functions are actually covered by either function-definition or member-declaration , which still use an optional decl-specifier-seq , so the changes to simple-declaration didn't affect them.

For conversion functions, the text in [class.conv.fct]/1 saying

A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall be neither a defining-type-specifier nor static .

forms the actual requirement: The [dcl.type] sentence excludes a conversion function's decl-type-specifier-seq from its usual requirement, so it doesn't say anything about what is and isn't legal. This [class.conv.fct] sentence gives the actual rule for this case.

A conversion function may be declared:

  • by a function-definition (if it has a body, including =default; or =delete; )
  • by a member-declaration (inside a class definition, if the declaration does not have a body)
  • by a simple-declaration or nodeclspec-function-declaration (if in a friend declaration, explicit specialization, or explicit instantiation)

A nodeclspec-function-declaration allows no initial specifiers, but the other three symbols all have a rule in which a decl-specifier-seq (either required or optional) is followed by either a declarator or an init-declarator-list . As you noted, the declarator for a conversion function contains the operator keyword followed by a type-specifier-seq . The declarator must also contain () or (void) or equivalent so that it declares a function with no arguments.

With a few more assumptions, the general form of a conversion function declaration is either

attribute-specifier-seq opt decl-specifier-seq opt operator type-specifier-seq conversion-declarator opt attribute-specifier-seq opt parameters-and-qualifiers virt-specifier-seq opt pure-specifier opt ;

or

attribute-specifier-seq opt decl-specifier-seq opt operator type-specifier-seq conversion-declarator opt attribute-specifier-seq opt parameters-and-qualifiers virt-specifier-seq opt function-body

So there's an optional decl-specifier-seq before the operator keyword and a required type-specifier-seq after it. It's the decl-specifier-seq which might not be present at all, and which must not contain a defining-type-specifier (because you don't put a type before the operator keyword) or static (because a conversion function must always be a non-static member). But the decl-specifier-seq may contain constexpr , inline , virtual , or explicit , or combinations of those.

The trouble you've noticed is that the wording of [dcl.type]/3 also means it technically doesn't apply to the type-specifier-seq in such a declaration which names the target type for the conversion. ( [dcl.pre]/4 clears up many similar statements about grammar symbols in a declaration, but doesn't apply to this case since there's no intervening scope involved.) We could still infer that a defining-type-specifier is needed from phrases in the Standard like "the type specified by the conversion-type-id ". But it would be better if the rule in [dcl.type]/3 applied to this type-specifier-seq like it does to most of them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM