函数原型和函数实现签名可以不一致地使用 const 吗?

2022-01-11 00:00:00 c header constants c++ function-prototypes

我喜欢尽可能将偶数值参数声明为 const,通过搜索 SO,我发现 这并不少见.像这样:

int add(const int a, const int b){...}

但我想知道: values 的 const 是我的函数的实现细节,而不是它的接口的一部分.所以把它放到原型中似乎没有必要.

上述函数的这个原型似乎工作得很好:

int add(int a, int b);

但我听说过一些问题,例如将 main 函数的 argc 声明为 const 可能会导致问题:

int main(const int argc, const char* const argv[])

那么这是否意味着 int add(int a, int b)int add(const int a, const int b) 根本就不一样?

如果技术上没问题,我应该这样做吗?我也可以在原型中省略变量名,但我没有,所以也许我也不应该省略 const?

解决方案

函数类型不一样是不行的,但是你需要知道什么是函数类型的一部分,什么不是'吨.在您的情况下,参数的 const 并不重要,因此函数类型相同,尽管 declaration 看起来与 definition.

在你的情况下是

<块引用>

8.3.5 函数[dcl.fct]

5 一个名称可用于单个范围内的多个不同功能;这是函数重载(第 13 条).函数的所有声明都应在返回类型和参数类型列表中完全一致.使用以下规则确定函数的类型.每个参数的类型(包括函数参数包)由其自己的 decl-specifier-seq 和声明符确定.后确定每个参数的类型,将T的数组"或返回T的函数"类型的任何参数分别调整为指向T的指针"或返回T的函数的指针".在生成参数类型列表后,任何修改参数类型的顶级 cv 限定符都会在形成函数类型时被删除.转换后的参数类型的结果列表以及省略号或函数参数包的存在与否是函数的参数类型列表.[注:此转换不影响参数的类型.例如,int(*)(const int p, decltype(p)*)int(*)(int, const int*) 是相同的类型.――尾注]

看起来,它需要一些解释,所以我们开始:在我们的例子中重要的句子是:在生成参数类型列表之后,任何修改参数类型的顶级 cv-qualifiers 被删除形成函数类型.

这意味着所有顶级 cv-qualifier 都被删除.为了解释顶级的含义,我将以非法的方式编写类型以强调 const 所指的内容:

  • const int = (const (int)) -> 这是一个顶级的const
  • const int* = ((const (int))*) -> 不是顶级,是二级
  • const int* const = (((const (int))*) const) -> 第二个 const 在顶部-级别
  • const int& = ((const (int))&) -> 不是顶级

我希望这能消除对函数类型的一些误解.

对于您的其他问题:我建议保持声明和定义相同,因为它可能会使人们感到困惑(就像这个问题所证明的那样;).

对于您给出的 main 示例:

int main( const int argc, const char* const argv[] )

是,根据上面的标准引用,相当于:

int main(int argc, const char* const* argv)

所以为 argv 添加的 const 最终不会作为顶级 const 被删除,因此它是格式错误的main 的函数类型,期望:

int main(int argc, char** argv)

关于省略参数名称的最后一个问题:我不会这样做,因为对我来说,它们是函数文档的一部分.它们传达函数的意图和语义(如果您明智地选择它们).

I like to declare even value parameters as const where possible, and by searching SO, I found that that's not too uncommon. Like this:

int add(const int a, const int b)
{
    ...
}

But I'm wondering: const for values is an implementation detail of my function, not part of it's interface. So putting it into the prototype seems unnecessary.

This prototype for the above function seems to work just fine:

int add(int a, int b);

Yet I've heard about issues that e.g. declaring the main function's argc as const can lead to problems:

int main(const int argc, const char* const argv[])

So does that mean that int add(int a, int b) and int add(const int a, const int b) are not identical after all?

And if it's technically OK, is that something I should do? I could also leave out variable names in the prototype, but I don't, so maybe I shouldn't leave out const either?

解决方案

It's not OK for the function type to differ, but you need to know what is part of the function type and what isn't. In your case, the const for the parameters is not significant, so the function type is the same, although the declaration looks like it differs from the definition.

In your case it's

8.3.5 Functions [dcl.fct]

5 A single name can be used for several different functions in a single scope; this is function overloading (Clause 13). All declarations for a function shall agree exactly in both the return type and the parameter-type-list. The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type "array of T" or "function returning T" is adjusted to be "pointer to T" or "pointer to function returning T," respectively. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function’s parameter-type-list. [ Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. ― end note ]

As it seems, it need some explanation, so here we go: The important sentence in our case is: After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type.

This means that all top-level cv-qualifier are remove. To explain what top-level means, I'll write types in an illegal way to emphasize what a const refers to:

  • const int = (const (int)) -> this is a top-level const
  • const int* = ((const (int))*) -> not top-level, it's at the second level
  • const int* const = (((const (int))*) const) -> the second const is at top-level
  • const int& = ((const (int))&) -> not top-level

I hope this clears some misconceptions about function types up.

For your other questions: I'd advise to keep the declaration and the definition identical, as it might confuse people (like evidenced by this question ;).

For the example of main that you gave:

int main( const int argc, const char* const argv[] )

is, according to the above quote from the standard, equivalent to:

int main( int argc, const char* const* argv )

so the added const for argv does not end up as a top-level const which is removed and it's therefore an ill-formed function type for main, which expects:

int main( int argc, char** argv )

You last question about leaving out the parameter names: I wouldn't do it because to me, they are part of the documentation of the function. They communicate the intent and the semantics of the function (if you choose them wisely).

相关文章