вторник, июля 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 комментариев:

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

    ОтветитьУдалить
  2. Анонимный10/7/07 14:21

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

    ОтветитьУдалить
  3. 2prokrust:

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

    ОтветитьУдалить
  4. Анонимный10/7/07 16:50

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

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

    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. И как в этом блоге код с подсветкой делать?

    ОтветитьУдалить
  6. Анонимный11/7/07 08:56

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

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

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

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

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

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

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

    ОтветитьУдалить
  10. Анонимный12/7/07 09:25

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

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

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

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

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

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


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

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

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

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

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

    ОтветитьУдалить
  13. Анонимный13/7/07 12:58

    Очень, очень эмоционально 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);

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

    ОтветитьУдалить
  14. Ога... угадал.
    Ога... 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.

    ОтветитьУдалить
  15. Анонимный22/7/07 17:29

    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));

    ОтветитьУдалить