вторник, июля 10, 2007

Объявление функции или экземпляра класса?

Интересная проблема из переписки. Есть код вида:


class CFoo
{
public:
CFoo(int i)
{
cout<<"CFoo::CFoo(int i)"<<endl;
}

CFoo()
{
cout<<"CFoo::CFoo()"<<endl;
}

void Print()
{
cout<<"CFoo::Print"<<endl;
}
};


int main(int argc, char* argv[])
{
CFoo foo1(1);
foo1.Print();

CFoo foo2;
foo2.Print();

CFoo foo3();
foo3.Print(); //ошибка
}


Этот код не будет откомпилирован. На foo3.Print() компилятор скажет что-то вроде.

error: request for member `Print' in `foo3', which is of non-class
type `CFoo ()()'

Тут все дело в том, что объявление вида
T f();
распознается компилятором не как объявление экземпляра класса, а как объявление функции. (Стандарт, 8.5/8).
То есть CFoo foo3(); - это функция без параметров, возвращающая CFoo.

Чтобы таки объявить экземпляр класса надо сделать так
T f = T();

или так

T f;

Ссылки по теме:
comp.lang.c++.moderated - ambiguity resolution between function declaration and object declaration

15 коммент.:

Dimchansky комментирует...

Иначе нельзя было бы функцию определить. :)

Анонимный комментирует...

Функцию можно определить так:
CFoo foo3(void);

Dimchansky комментирует...

2prokrust:

т.е. хотелось бы, что если явно указывается хоть какой-то тип в скобках, то - функция, а если нет, то конструктор объекта?

Анонимный комментирует...

Вообще хотелось бы чтобы было более гибко: внутри функции(метода) по умолчанию интерпретировалось бы как конструктор объекта, а вне - как функция.

archimed7592 комментирует...

Да тут проблема несколько глубже и настоящие грабли там, где мы их совсем не ждём:

struct A
{
A(const std::string &)
{ }

void foo()
{ std::cout << "A::foo()" << std::endl; }
};


int main()
{
const char *cstr = "cstr";
A obj(cstr);
A func(std::string(cstr)); // интерпретируется как объявление ф-ции, принимающей именованный параметр string
A func2(std::string cstr); // эквивалентно предыдущей строке
A obj2((std::string(cstr))); // если всё же хочется определить переменную, то всё это нужно взять в скобки
obj.foo();
func.foo(); // error
func2.foo(); // error
obj2.foo();

return 0;
}

Вообще хотелось бы чтобы было более гибко: внутри функции(метода) по умолчанию интерпретировалось бы как конструктор объекта, а вне - как функция.
А по мне, так лучше так, как есть сейчас. Во-первых, никаких реальных проблем с этим нет. Во-вторых, грамматика языка и так очень сложна. Зачем нагружать её контекстом "внутри ф-ции или нет"?
В-третьих, такая запись(определение переменной) вполне справедлива не только в ф-ции, но и в namespace-scope. Т.е. одна и та же запись будет означать разные вещи в зависимости от того, где она находится.

ps. И как в этом блоге код с подсветкой делать?

Анонимный комментирует...

Компилер это принимает за объяву функции:
A func(std::string(cstr));
Я в обмороке. При чем и мелко-мягкий и гнусный здесь едины!
А вообще, если делать по уму, то при возможности двойной интерпретации должен вылезать варнинг.
Зачем нагружать её контекстом "внутри ф-ции или нет"?
С чем я как-то столкнулся на практике: Изменяя текст проги убрал параметры конструктора. Скобки ессно оставил. После чего доолго не мог понять куда делась переменная. Так тоже быть не должно!

Alena комментирует...

ps. И как в этом блоге код с подсветкой делать?

В комментариях, увы, никак. :-(

archimed7592 комментирует...

Компилер это принимает за объяву функции:
A func(std::string(cstr));
Я в обмороке. При чем и мелко-мягкий и гнусный здесь едины!

ЛЮБОЙ, соответствующий Стандарту компилятор будет с ними солидарен.

С чем я как-то столкнулся на практике: Изменяя текст проги убрал параметры конструктора. Скобки ессно оставил. После чего доолго не мог понять куда делась переменная. Так тоже быть не должно!
Интересно... почему люди думают только о себе? <_<
Наверное уж, если бы было красивое и эффективное решение этой проблемы, её бы давно уже решили бы. В частности ты можешь предложить своё решение и отправить т.н. proposal в WG21...

archimed7592 комментирует...

В комментариях, увы, никак. :-(
Хоть бы тег pre разрешили...

Анонимный комментирует...

ЛЮБОЙ, соответствующий Стандарту компилятор будет с ними солидарен.
Много букф, не осилил. Выдержку из стандарта к этому случаю, пожалуйста! Стандартный компилятор должен предпочесть конструктор. А что подумает об этой строке любой кодер просто очевидно.
Интересно... почему люди думают только о себе?
Интересная логика - я привожу реальный пример недостатка текущего правила, меня в ответ обвиняют в себялюбии.
Наверное уж, если бы было красивое и эффективное решение этой проблемы, её бы давно уже решили бы.
Вера в то, что там наверху умнее, меня всегда забавляла. В данном случае требуется быть не умным, а здравомыслящим. Переменные с конструктором в функциях я объявляю постоянно, а вот описание прототипа еще не разу в жизни.

archimed7592 комментирует...

Много букф, не осилил
Где? В стандарте? Ну неудивительно - его же составляли нездравомыслящие идиоты, которые тебя всегда так забавляли...

Выдержку из стандарта к этому случаю, пожалуйста!
8.3/6 устроит? Или тебе по полочкам всё разложить?

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

А что подумает об этой строке любой кодер просто очевидно.
Любой - не есть хороший.

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


Вера в то, что там наверху умнее, меня всегда забавляла. В данном случае требуется быть не умным, а здравомыслящим.
Может быть месье здравомыслящий приведёт здесь свой вариант грамматики для языка С++? Мы с удовольствием послушаем и приведём тебе аргументы почему твоя грамматика будет хуже текущей.

Alena комментирует...

Где? В стандарте? Ну неудивительно - его же составляли нездравомыслящие идиоты, которые тебя всегда так забавляли...

Что-то спор плавно перетек в личные наезды, давайте вернем его к обсуждению Стандарта.

8.3/6 устроит? Или тебе по полочкам всё разложить?

А я вот полезла грамматику смотреть и не нашла там как будет распарсиваться этот вариант...

Анонимный комментирует...

Очень, очень эмоционально archimed7592.
8.3.6 Default arguments не имеет никого отношения к данному случаю.
Поискал сам:
8.2 Ambiguity resolution
The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8 can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8, the resolution is to consider any construct that could possibly be a declaration a declaration. [ Note: a declaration can be explicitly disambiguated by a nonfunction-style cast, by a = to indicate initialization or by removing the redundant parentheses around the parameter name. ] [ 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 ]
Резюмируем - действительно, создатели компиляторов придерживались стандарта, archimed7592 угадал.
Откуда же такое странное решение? -Смотрим пункт 6.8 Там есть пример:
class T;
T(a); //declaration
Если а уже объявлена как тип, то так мы объявим переменную а. Создавать же так объект имеет мало смысла (хотя я мог бы найти). Проверку есть на самом деле тип а и нужно ли понимать именно так конечно решили не проводить, упростив понимание.
Вот это решение и было распространено и на параметры функции. Очень логично, но при этом забыли что создание объекта в параметрах очень распространено. Получается что писать код надо так:
A func3((std::string)cstr);

ЗЫ. Так что я могу предъявить только одну притензию к компиляторам: нет предупреждения о возможной двойной интерпретации. Это очень плохо.

archimed7592 комментирует...

Ога... угадал.
Ога... 8.3.6 отношения не имеет.
А вот 8.3/6(пункт 8.3, параграф 6) имеет и непосредственное.

In a declaration T D where D has the form
( D1 )
the type of the contained declarator-id is the same as that of the contained declarator-id in the declaration
T D1
Parentheses do not alter the type of the embedded declarator-id, but they can alter the binding of complex declarators.

Анонимный комментирует...

archimed7592
А по мне, так лучше так, как есть сейчас. Во-первых, никаких реальных проблем с этим нет.

На самом деле проблема есть. Определение в форме "T t;" не даёт value-initialized объекта. Т.е. если T POD типа, то t не будет инициализировано (там будет мусор).
Определение в форме T t = T(); не допускает применения оного в отношении non-copyconstructible типов. Такое ограничение может быть неприемлемым. Из известных мне способов только инициализация агрегатов позволяет безопасно создавать на стеке value-initialized объект, который впоследствии можно модифицировать:

T t[1] = {};
/* *t из value-initialized */

но изящным этот способ как-то не назовёшь.

Я бы предпочёл записывать как-то так (раз уж выражения типа void в C++ разрешены):

T t(void(0));