Сложная информация о самом развязном параде

Мой вопрос заключается в том, как следующую строку можно анализировать как объявление функции:

vector<int> v(istream_iterator<int>(cin), istream_iterator<int>());

Я понимаю большинство деталей самого Vexing Parse и почему второй временный итератор можно интерпретировать как тип, который возвращает функцию итератора и не принимает никаких аргументов, но то, что я не получаю, - это то, почему первый временный Итератор может быть интерпретирован как тип. Какой тип он представляет? Я думал, что это будет какой-то тип функции, но я не вижу, как будет использоваться имя cin. Объявляет ли он, что параметр istream_iterator<int> с именем cin? Если это так, означает ли это, что вы можете произвольно заключить в скобки имена аргументов функций? И если да, то почему?

Ответы

Ответ 1

istream_iterator<int>(cin) точно такой же, как istream_iterator<int> cin, но с избыточными паранами. Этот синтаксис декларатора был унаследован от C, и я думаю, что даже изобретатель C (Кен Томпсон?) Описал это как ошибку.

Ответ 2

Я уже говорил, что мне нравится Кланг (много)?

Просто попробуйте следующий (упрощенный код)

#include <vector>

void foo(std::vector<int>);

int main() {
  std::vector<int> v(int(i), int());
  foo(v);
}

В недавно обновленном LLVM Try Out (ну, он просто перешел от llvm-gcc к clang).

И вы получите:

/tmp/webcompile/_21483_0.cc:6:21: warning: parentheses were disambiguated
                                           as a function declarator
  std::vector<int> v(int(i), int());
                    ^~~~~~~~~~~~~~~
/tmp/webcompile/_21483_0.cc:7:3: error: no matching function for call to 'foo'
  foo(v);
  ^~~
/tmp/webcompile/_21483_0.cc:3:6: note: candidate function not viable:
     no known conversion from 'std::vector<int> (int, int (*)())'
     to 'std::vector<int>' for 1st argument
void foo(std::vector<int>);
     ^
3 diagnostics generated.

И поэтому @john прав, int(i) интерпретируется как int i, то есть именованный параметр функции.

Ответ 3

Да, это имя параметра. И да, вы можете добавить набор круглых скобок, потому что иногда вам приходится.

Если параметр является указателем на функцию, void (*f)() вам нужно написать его так.

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

Ответ 4

В стандарте (2003) есть раздел под названием Ambiguity resolution, посвященный таким синтаксисам. Думаю, мне не нужно объяснять это дальше, если вы сами читаете раздел, потому что он очень понятен множеству примеров!

Итак, вот вы:

8.2 Разрешение неоднозначности [dcl.ambig.res]

1 - Неопределенность, возникающая из-за сходства между приведением в стиле функции и декларацией, упомянутой в 6.8, может также возникать в контексте объявления. В этом контексте выбор между объявлением функции с избыточным набором круглых скобок вокруг имени параметра и объявлением объекта с использованием функции-стиля в качестве инициализатора. Так же как и для двусмысленностей, упомянутых в 6.8, резолюция состоит в том, чтобы рассмотреть любую конструкцию, которая может быть объявлением декларацией. [Примечание: объявление может быть явно устранено с помощью неэффективного стиля, а = указать инициализации или путем удаления избыточных круглых скобок вокруг имени параметра. ]

[Example:

struct S {
    S(int);
};

void foo(double a)
{
   S w(int(a));  // function declaration
   S x(int());   // function declaration
   S y((int)a);  // object declaration
   S z = int(a); // object declaration
}
—end example]

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

3- [Example:

#include <cstddef>

char *p;
void *operator new(size_t, int);

void foo() {
    const int x = 63;
    new (int(*p)) int; // new-placement expression
    new (int(*[x]));   // new type-id
}

//4 - For another example,

template <class T>
struct S {
    T *p;
};
S<int()> x;  // type-id
S<int(1)> y; // expression (ill-formed)

//5 - For another example,
void foo()
{
   sizeof(int(1)); // expression
   sizeof(int()); // type-id (ill-formed)
}

//6 - For another example,
void foo()
{
   (int(1)); //expression
   (int())1; //type-id (ill-formed)
}
—end example]

7 - Еще одна двусмысленность возникает в объявлении параметра объявления объявления функции или в типе-идентификаторе, который является операндом оператора sizeof или typeid, когда имя типа вложено в круглые скобки. В этом случае выбор между объявлением параметра указателя типа на функцию и объявлением параметра с резервными скобками вокруг идентификатора declarator. Резолюция должна учитывать имя типа как simple-type-specifier, а не идентификатор declarator.

[Example:

class C { };
void f(int(C)) { }    // void f(int (*fp)(C c)) { }
                      // not: void f(int C);
int g(C);
void foo() {
    f(1); //error: cannot convert 1 to function pointer
    f(g); //OK
}

//For another example,
class C { };
void h(int *(C[10]));  // void h(int *(*_fp)(C _parm[10]));
                      // not: void h(int *C[10]);

—end example]