关于c ++:为什么不能用参数构造条件中定义的变量?


Why can't variables defined in a conditional be constructed with arguments?

问题很简单。 为什么编译:

1
2
bool b(true);
if (b) { /* */ }

并编译:

1
if (bool b = true) { /* */ }

但这不是:

1
if (bool b(true)) { /* */ }

在我的真实代码中,我需要构造一个对象并对其进行测试,同时还要在if块结束时销毁它。 基本上,我正在寻找这样的东西:

1
2
3
4
5
6
{
    Dingus dingus(another_dingus);
    if (dingus) {
        // ...
    }
}

当然,这可以工作:

1
if (Dingus dingus = another_dingus) { /* */ }

但是,然后我构造一个Dingus并对其调用operator=。 在我看来,我可以使用我喜欢的任何构造函数来构造对象。

但是我很困惑为什么这在语法上是不正确的。 我已经用G ++和MSVC ++进行了测试,他们都抱怨这种构造,所以我确定它是规范的一部分,但是我对此的原因以及可能存在的不丑陋的解决方法感到好奇。


这有点技术性。没有理由不能允许您想要的东西,事实并非如此。这是语法。

if语句是选择语句,采用语法形式:

1
if (condition) statement

在这里,condition可以是:

  • expression
  • type-specifier-seq declarator = assignment-expression

那里有它。允许在条件中进行声明是一种特殊情况,它必须遵循该格式,否则您的程序格式不正确。他们可能允许直接初始化而不是复制初始化,但是现在并没有任何动机。正如Johannes Schaub指出的那样,此更改将破坏现有代码,因此几乎永远不会发生。

Let_Me_Be指出C ++ 11添加了第三种形式(我在这里忽略属性):

1
decl-specifier-seq declarator braced-init-list

所以if (bool b{true})很好。 (这不可能破坏任何有效的现有代码。)

请注意,您的问题似乎与效率有关:请放心。编译器将忽略临时值,而直接直接构造左侧。但是,这要求您的类型是可复制的(或在C ++ 11中可移动)。


应该注意的是,if(functor(f)(123)) ...;不再是带有参数123的匿名仿函数的调用,而是声明由123初始化的仿函数。

而且我认为为此小功能引入此类陷阱是不值得的。

由于可能不清楚上述含义,因此让我们更深入地了解一下。首先请记住,允许在声明符周围加上括号,包括在直接位于声明名称周围的简并情况下:

1
2
3
4
5
int(n) = 0;
// same: int n = 0;

int(n)(0);
// same: int n(0);

这两个带括号的版本都不明确,因为第一个可能是赋值,第二个可能是函数调用。但两者也可以是声明。标准说它们是声明。

如果我们允许在条件中使用paren初始化程序,那么我们也将后一种歧义引入条件中,就像语句情况一样。因此,在支持该功能之后,我们将使当今使用的有效条件表达式成为声明。考虑

1
2
3
4
5
6
7
8
9
10
11
12
typedef bool(*handler_type)(int);

bool f(int) { /* ... */ }
bool f(int, int) { /* ... */ }

void call_it() {
   // user wants to call f(int), but it is overloaded!
   // -> user tries a cast...
   if(handler_type(f)(0)) {
     /* ... */
   }
}

您认为会发生什么?当然,它永远不会进入if主体,因为它总是声明一个空指针。它从不调用函数f。没有"功能",它将正确调用f,因为我们没有歧义。这不仅限于(f),还不仅限于(*f)(声明指针),(&f)(声明引用)等。

再说一遍:我们是否想要像这样的小功能价格那样的陷阱?我不知道有多少人甚至知道他们可以在某种情况下声明东西。


这是语言语法限制。 if语句中括号中的位可以是表达式,也可以是必须具有以下形式之一的受限声明形式:

attribute-specifier-seq OPT decl-specifier-seq declarator = initializer-clause

attribute-specifier-seq OPT decl-specifier-seq declarator braced-init-list

不允许其他形式的声明。请注意,这里没有分配,只有复制初始化。

如果要在select语句条件下直接初始化对象,则可以使用大括号初始化列表的新形式(自C ++ 11起):

1
2
3
4
if (Type var { init })
{
    // ...
}


这是您问题的解决方案,尽管它不能完全回答问题:

1
if (bool b = bool(true)) { /* */ }

它没有执行您认为正在执行的操作-在这种情况下,bool(true)不会调用构造函数,而是在执行强制转换。例如:

1
return foo(0);

是相同的:

1
return static_cast<foo>(0); // or (foo)0

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct foo {
  foo(int x) {
    std::cout <<"ctor\
"
;
  }
  foo(const foo& x) {
    std::cout <<"copy ctor\
"
;
  }
  operator bool() {
    return true;
  }

};

int main(int, char**) {
  if (foo x = foo(1)) { /* */ }
}

打印" ctor"。由于复制省略,不调用复制构造函数。


因为" ="被定义为带有两个参数的函数,并且-在执行其工作后-返回第一个参数的值。构造函数不返回值。