在不带参数的函数声明中指定void的使用是否解决了最令人烦恼的解析?

时间:2017-01-17 18:37:11

标签: c++ void kernighan-and-ritchie most-vexing-parse

最令人烦恼的解析是否根植于是否使用void作为不带参数的函数声明的参数的歧义?

例如,以下代码在g ++(v7.2.1)和Xcode(Apple LLVM版本7.0.2(clang-700.1.81))编译器上编译时没有错误,运行正常。

#include <iostream>

int asdf(void);
int asdf(int a) {
    return a;
}
int main() {
    std::cout << asdf(6) << std::endl; //-> 6
    return 0;
}

这一直回到ANSI-C标准之前,其中空参数列表表示任意数量的参数。它今天困扰着我们,在编译器中具有向后兼容性,这似乎对这个问题有点困惑。如果上述不至少产生警告,如果没有抛出错误?

在这里,我有一个顿悟(或许是一个白日梦!)。可能是最令人烦恼的解析根源于在空函数声明列表中使用void的模糊性吗?

另一个例子,引用以下维基百科示例:

class Timer {
 public:
  Timer();
};

class TimeKeeper {
 public:
  TimeKeeper(const Timer& t);

  int get_time();
};

int main() {
  TimeKeeper time_keeper(Timer());
  return time_keeper.get_time();
}
  

该行

TimeKeeper time_keeper(Timer());
     

似乎含糊不清,因为它可以解释为

     
      
  • 类TimeKeeper的变量time_keeper的变量定义,使用类Timer或
  • 的匿名实例初始化   
  • 函数time_keeper的函数声明,它返回一个TimeKeeper类型的对象,并且有一个(未命名的)参数,   是指向函数返回类型Timer的指针(并且没有输入)。   (参见函数对象#In C和C ++)
  •   

参考: https://en.wikipedia.org/wiki/Most_vexing_parse

现在,如果我们在声明一个没有变量的函数时指定必须使用void,那么这不会消除(不是hackishly,从根本上删除!)歧义吗?有问题的行将成为以下内容,毫无疑问它是一个函数声明:

int main() {
  TimeKeeper time_keeper(Timer(void));
  return time_keeper.get_time();
}

这一直是回到KnR,讨论了这个问题:

  

由于getline和copy的专用版本没有参数,   逻辑会建议他们的原型在文件的开头   应为getline()copy()。但为了兼容性   旧的C程序标准将空列表作为旧式   声明,并关闭所有参数列表检查;这个单词   void必须用于显式空列表。   [Kernighan&amp; Richie,C编程语言,1988,Pgs 32-33]

和..

  

空参数列表的特殊含义是允许的   使用新编译器编译的旧C程序。但这不是一个坏主意   将它与新程序一起使用。如果函数接受参数,则声明   他们;如果没有参数,请使用void [同上,Pg。 73]

参考: is f(void) deprecated in modern C and C++

2 个答案:

答案 0 :(得分:2)

函数定义也是一个声明。这意味着什么时候有

int asdf(void);
int asdf(int a) {
    return a;
}

您已宣布两个asdf版本,一个版本为int而另一个版本为“{1}}”。然后,在使用

时继续使用int版本
std::cout << asdf(6) << std::endl; //-> 6

由于定义了int版本,因此没有任何问题。

如果您尝试使用

,会出现什么问题
asdf();

当你这样做,因为它已被声明但未定义你应该得到

  

未定义对`asdf()&#39;

的引用

由于没有定义。这与最令人烦恼的解析无关,而只是函数声明和定义之间的区别。

答案 1 :(得分:2)

您的提案不会完全消除歧义。

考虑:

#include <string>

struct Foo { ... };

int main(int argc, char **argv) {
    Foo bar(std::string(argv[1]));
}

这似乎可以声明类型为bar的局部变量Foo,并将std::string传递给其构造函数。

但实际上它意味着:

Foo bar(std::string *argv);

即。声明bar作为一个函数,使用“std::string指针”类型的参数并返回Foo

无法通过添加void修复此示例。