Подтвердить что ты не робот

Почему переменные, определенные в условном выражении, не могут быть построены с аргументами?

Вопрос прост. Почему это компилируется:

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

И этот компилятор:

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

Но не это:

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

В моем реальном коде мне нужно построить объект и протестировать его, а также уничтожить его при завершении if-блока. В принципе, я ищу что-то вроде этого:

{
    Dingus dingus(another_dingus);
    if (dingus) {
        // ...
    }
}

Конечно, это сработало бы:

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

Но потом я создаю Dingus и вызываю operator= на нем. Мне кажется логичным, что я смогу построить объект, используя любой конструктор, который мне нравится.

Но я не понимаю, почему это не является грамматически правильным. Я тестировал с g++ и MSVС++, и они оба жалуются на эту конструкцию, поэтому я уверен, что это часть спецификации, но мне любопытно относиться к рассуждениям об этом и о том, какие не уродливые обходные пути могут быть.

4b9b3361

Ответ 1

Это немного технично. Там нет причин, почему то, что вы хотите, не может быть разрешено, просто нет. Это грамматика.

Инструкция if представляет собой инструкцию выбора, и она принимает грамматическую форму:

if (condition) statement

Здесь condition может быть:

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

И у вас это есть. Разрешить объявление в состоянии является особым случаем, и он должен следовать этой форме или ваша программа плохо сформирована. Вероятно, они могли бы позволить прямое инициализацию вместо инициализации копий, но сейчас нет никакой мотивации. Как указывает Йоханнес Шауб, это изменение нарушит существующий код, поэтому его почти никогда не произойдет.

Let_Me_Be отмечает, что С++ 11 добавил третью форму (я игнорирую здесь атрибуты):

decl-specifier-seq declarator braced-init-list

Итак, if (bool b{true}) отлично. (Это не может сломать действующий существующий код.)


Обратите внимание, что ваш вопрос, похоже, связан с эффективностью: не беспокойтесь. Компилятор вернет временное значение и просто построит левую сторону напрямую. Это, однако, требует, чтобы ваш тип можно было копировать (или перемещать на С++ 11).

Ответ 2

Следует отметить, что if(functor(f)(123)) ...; не будет теперь вызовом анонимного функтора с аргументом 123, но объявит функтор, инициализированный 123.

И я думаю, что введение таких ловушек для этой маленькой функции не стоит.


Так как может быть неясно, что это значит, позвольте взглянуть глубже. Сначала помните, что круглые скобки вокруг декларатора разрешены, в том числе для вырожденного случая нахождения непосредственно вокруг объявленного имени:

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

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

Обе версии в скобках неоднозначны, поскольку первая может быть назначением, а вторая может быть вызовом функции. Но оба могут также быть декларациями. Стандарт говорит, что они являются декларациями.

Если мы разрешим инициализаторы парнов в условиях, тогда мы введем последнюю неоднозначность в условия тоже, как и в случае утверждения. Таким образом, мы будем делать допустимые выражения условий, которые используются в настоящее время в объявлениях после поддержки функции. Рассмотрим

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) (объявляет ссылку) и др.

Опять же: хотим ли мы таких ловушек, как цена за такую ​​небольшую особенность? Я не знаю, сколько людей даже знают, что могут объявить материал в состоянии.

Ответ 3

Это ограничение грамматики языка. Бит в круглых скобках в выражении if может быть выражением или может быть ограниченной формой декларации, которая должна иметь одну из форм:

атрибут-спецификатор-seq OPT decl-specifier-seq declarator = initializer-clause

атрибут-спецификатор-seq OPT decl-specifier-seq declarator braced-init-list

Никакие другие формы объявления не допускаются. Обратите внимание, что здесь нет назначения, только копирование-инициализация.

Если вы хотите напрямую инициализировать объект в условии select select, вы используете новую форму списка с привязкой к init (начиная с С++ 11):

if (Type var { init })
{
    // ...
}

Ответ 4

Здесь решение вашей проблемы, хотя оно не совсем отвечает на вопрос:

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

Он не делает то, что, по вашему мнению, делает - bool (true) не вызывает конструктор в этом случае, выполняя приведение. Например:

return foo(0);

совпадает с:

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

Тест:

struct foo {
  foo(int x) {
    std::cout << "ctor\n";
  }
  foo(const foo& x) {
    std::cout << "copy ctor\n";
  }
  operator bool() {
    return true;
  }

};

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

печатает "ctor". Не вызывает конструктор копирования из-за копирования.

Ответ 5

потому что "=" определяется как функция, которая принимает два аргумента и - после выполнения своего задания - возвращает значение первого. Конструктор не возвращает значение.