C++, зогатка.

bleyman


#include <stdio.h>

typedef int (*f_ptrconst int x, const int y);

// error: uninitialized const member `<anonymous struct>::f'
//struct {
// const char * s;
// const f_ptr f;
//} test00 = { };

struct {
    const char * s;
    f_ptr f; // non-const, OK too.
} test0 = { };

struct {
    const char * s;
    const f_ptr f;
} test1 = { "lol", NULL}; // OK

struct {
    const char * s;
    const f_ptr * f; // double ptr apparently.
} test2 = { }; // OK

struct {
    const char * s;
    const f_ptr ** f; // triple ptr?
} test3 = { }; // OK


int main(void)
{
    int x = 0;
// x += (&(test0.f0, 0); // error: `int (**int, int&test0 + 4u)' cannot be used as a function
    x += test0.f(0, 0);
    x += (*(test0.f0, 0);
    x += (**(test0.f0, 0);
    x += (***(test0.f0, 0);

// x += (&(test1.f0, 0); // error: `int (* const*int, int&test1 + 4u)' cannot be used as a function
    x += test1.f(0, 0);
    x += (*(test1.f0, 0);
    x += (**(test1.f0, 0);
    x += (***(test1.f0, 0);

// x += (&(test2.f0, 0); // error: `int (* const**int, int&test2 + 4u)' cannot be used as a function
// x += test2.f(0, 0); // error: `test2.<anonymous struct>::f' cannot be used as a function
    x += (*(test2.f0, 0);
    x += (**(test2.f0, 0);
    x += (***(test2.f0, 0);

// x += (&(test3.f0, 0); // error: `int (* const***int, int&test3 + 4u)' cannot be used as a function
// x += test3.f(0, 0); // error: test3.<anonymous struct>::f' cannot be used as a function
// x += (*(test3.f0, 0); // `*test3.<anonymous struct>::f' cannot be used as a function
    x += (**(test3.f0, 0);
    x += (***(test3.f0, 0);

    return x;
}


Суть зогатки: меня не очень удивляет, что указатели на функции можно бесконечно дереференсить и они по-прежнему остаются как бы функциями.
Но меня дико удивляет, почему простой указатель на функцию отказывается дефолт-инициализироваться (если я правильно понимаю, гарантированно в NULL). Причём только если он ещё и const.
Проверял в gcc 3.4.6, 4.2.2.

bleyman

vs2010 несколько более многословна: http://msdn.microsoft.com/en-us/library/9zkz8dx6%28v=VS.100%...
По-моему, гонит! Какой такой дефолтный конструктор должен быть у константного указателя на функцию, который внезапно есть у неконстантных, а тут — нет?

bleyman

On a completely unrelated note, если пытаться использовать хитроумную магею типа

template <int table_size>
int compare_messages(const int (&comp_table)[table_size], ...)

То передача по ссылке обязательна, иначе оказывается компилиться. Впрочем, отлично компилится если указать размер ручками, на call site, в смысле, явно инстанцировать темплейт. И массив передаётся по ссылке, а не по значению!
Видимо, дело в том, что "int x[n]" типом не является (как бы порой не казалось обратное потому что совместимость с С, а вот "int (&x)[n]" — полноценный тип.
Плюсы такие няшные!

yolki

#include <stdio.h>
typedef int (*f_ptrconst int x, const int y);
// error: uninitialized const member `<anonymous struct>::f'
//struct {
// const char * s;
// const f_ptr f;
//} test00 = { };
я в плюсах не гульбарий, сначала не обратил внимания.
по сишному - всё ч0тко.

kokoc88

Но меня дико удивляет, почему простой указатель на функцию отказывается дефолт-инициализироваться (если я правильно понимаю, гарантированно в NULL). Причём только если он ещё и const.
Замени f_ptr на int, у тебя будет то же самое. Разница в том, куда применяется const: поле типа const char* можно перезаписать, а const int или const f_ptr - нельзя.

okis

Так const относится к char, а не к *. Если сделать char* const, то нельзя будет переписать.

bleyman

ААААААААААА
Ок, понял. Ты прав.
typedef char * cptr;

struct {
const cptr c1;
const char * c2;
} test00 = { };

Одно из них выдаёт ошибку, другое — нет, естественно.
Это даже логично, если подумать. Типа, если развернуть тайпдеф и явно указать приоритеты операций, то получится как бы это:

const cptr c1; <==> (const (char * c;
const char * c1; <==> const char) *) c;

Что не является валидным кодом, кстати говоря, нельзя скобочки в декларации-то. Поэтому корректно разворачивается тайпдеф через жопу: "char * const c;"
ок, спасибо, я кажется внезапно намного лучше стал понимать, как работает const!
Кстати интересно, а чё в С-то работает? То есть, почему в C++ внезапно запретили инициализировать константные поля структур вот так вот?

kokoc88

Так const относится к char, а не к *. Если сделать char* const, то нельзя будет переписать.
На этом и основывался мой ответ.

enochka1145

// Кстати интересно, а чё в С-то работает? То есть, почему в C++ внезапно запретили инициализировать константные поля структур вот так вот?
Тред не читал, но :)
В C смысл (семантика) const иная. Что-то типа "эта ячейка находится в ПЗУ".

yolki

про const не прав.

bleyman

Разница в том, куда применяется const: поле типа const char* можно перезаписать, а const int или const f_ptr - нельзя.
Подожди, стоп, я внезапно понял, что вопрос-то не только в этом.
Почему компилятор волнует, что там можно перезаписать, а что — нет?
Инициализирует-то он всё совершенно нормально. Причём неконстантные поля для которых не указан инициализатор, замечательно инициализирует дефолтными значениями, (по сишному стандарту, сквозь дебри плюсового я что-то не в состоянии продираться, но там то же самое очевидно).
Проблема не в том, что ему не хочется инициализировать константные поля (как мне внезапно вчера показалось проблема в том, что ему почему-то не хочется инициализировать константные поля дефолтными значениями.
Почему? Какому-то мудаку из комитета показалось, что я не должен этого хотеть, и он потребовал сделать это даже не варнингом, а еггогом?

kokoc88

Почему? Какому-то мудаку из комитета показалось, что я не должен этого хотеть, и он потребовал сделать это даже не варнингом, а еггогом?
В Си++ поля классов и структур не инициализируются никакими дефолтными значениями. Забыл, для чего это сделано, для скорости что ли...

slonishka

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

enochka1145

6 минусов и ни одного внятного объяснения? Форум.локал... :grin:
В тред приглашается Thinking in C++:
The const was taken from C++ and incorporated into Standard C, albeit quite differently. In C, the compiler treats a const just like a variable that has a special tag attached that says “Don’t change me.” When you define a const in C, the compiler creates storage for it, so if you define more than one const with the same name in two different files (or put the definition in a header file the linker will generate error messages about conflicts. The intended use of const in C is quite different from its intended use in C++ (in short, it’s nicer in C++).
Не будет компилятор C менять ячейку const, так что её смело можно разместить в ROM. А вот C++ - может (надо const volatile, чтобы не менял).

bleyman

Да ну не может такого быть, потому что это сломало бы совместимость с С совершенно жутким образом.
Смотри, в С фишка такая. Если ты просто заводишь локальную переменную какого-то типа, то она не инициализируется (а глобальные переменные инициализируются всегда, кстати).
Однако как только ты её принудительно инициализируешь, хоть как "struct somestruct = {};", все поля и подполя этой структуры рекурсивно инициализируются дефолтным значением. Нулём, как правило. Причём в C99 ваще дико мощные шняжки есть, вот смотри http://pastebin.com/1aRcCz3E
Аналогично для инициализированных массивов, инициализированнных структур с массивами внутри — всё инициализируется либо указанными тобой значениями, либо дефолтными.
Прочитай, короче, 7.8 C99 standard, это три странички определений, плюс ещё две странички незамутнённого щастья в виде примеров, типа:
struct { int a[3], b; } w[] =
     { [0].a = {1}, [1].a[0] = 2 };

Чего делать нельзя — это указывать в самих структурах какие-то недефолтные дефолтные значения, типа "struct s { int a = 1; int b};".
Соответственно, в плюсах правила инициализации для POD типов обязаны быть такими же — если есть инициализатор, то указанные элементы инициализируются указанными значениями, всё остальное — эквивалентами нуля.
Совершенно непонятно, с чего бы вдруг кто-то решил запретить эту дефолтную инициализацию нулём для константных полей. У меня есть два варианта — либо это как-то дико конфликтует с какой-то другой фишкой плюсов (ну, там, не знаю, они на самом деле даже для POD генерят дефолтный конструктор, а там немножко другие требования либо какой-то мудак из стандартного комитета решил, что если я не указал инициализатор для константного поля, то это я наверное ошибся, и компилятор должен мне об этом ненавязчиво намекнуть. В последнем случае это дикое мудачество, ибо вот у меня совершенно нормальный пример — я хочу завести себе константный массивчег с описанием особой херни, и некоторые из полей в инициализаторе предпочёл бы не указывать, пусть остаются нуллами.
: и чо? Твоя цитата совершенно не проливает свет на смысл конст в плюсах, особенно в данном случае. И что С++ "может"? Ты опечатался, или имел в виду, что может поменять? Я, короче, не могу пропарсить твою последнюю фразу!

vall

Не будет компилятор C менять ячейку const, так что её смело можно разместить в ROM.
ORLY?
const int a = 0;
int *b = (void *)&a;
*b = 1;

bleyman

YA RLY.
Он может и будет разместить её в ROM, а на твоё высказывание ты словишь undefined behaviour, за присвоение пойнтеров несовместимого типа.
EDIT:

5 If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined. If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.
У меня проблемы со второй половиной утверждения Жюнита, чо в плюсах-то происходит?

vall

мой претензия к части "не будет менять" а не к "может разместить в ром"

enochka1145

Слушьте, у меня башка гудит, хотелось бы вопросы покороче. Семантика const в плюсах ИМХО тривиальна, простите за снобизм. В сях const никогда не использовал, но запомнил вышеприведённый отрывок.

vall

да, семантика такая, но прострелить себе ноги никто не запрещает.

enochka1145

Ну, это же C++ - каждый имеет право идти в ад собственной дорогой.

Serab

Если это a будет статическим, то на винде должно случиться AV, если автоматическим, то MSVC даст поменять через указатель, но в оптимизированном коде вместо a будет подставляться константа в сгенеренном коде, поэтому через указатель будем получать измененное значение, а через a — из объявления. Кто-нибудь может и правда в «ROM» поместит и будет AV/segfault при таких штуках. Можно gcc потестить.

Serab

ну да, на g++ даже в -O0 аналогичное поведение: на статических segfault, автоматические дает менять, но константы подставляет прямо в код.

Serab

Так я это к чему, в чем отличие с C должно быть-то?

bleyman

Семантика const в плюсах ИМХО тривиальна, простите за снобизм. В сях const никогда не использовал, но запомнил вышеприведённый отрывок.
ЧТО-ТО ТЫ ПИЗДИШЬ.
Ок, я откопал C++2003 standard (он кстати есть в интернетах совершенно не драфт буквально первой же ссылкой!) и посмотрел в него. Раздел 7.1.5.1, интересные места:
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.
If an attempt is made to refer to an object defined with a volatile-qualified type through the use of an lvalue with a non-volatile-qualified type, the program behaviour is undefined.
[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the same in C + + as they are in C.]
Ну и где тут про то, что конст поля можно менять?
Единственное отличие от С, как я понимаю, вот в этом: An object declared in namespace scope with a const-qualified type has internal linkage unless it is explicitly declared extern or unless it was previously declared to have external linkage.
В С этого нет, и внимательный взгляд на твою цитату из Thinking C++ вызывает жуткие подозрения, что автор сделал СОВЕРШЕННО неверные выводы о природе конст в си и плюсах на основе эффектов, производимых этим немудрящим отличием. Что де в плюсах под конст не выделяется память или ещё что-то. Вот в C# это так, кстати. Ну или может быть тебе удалось выдрать цитату дичайше из конекста.
Обесни, короче, "тривиальную семантику" конст в плюсах, как ты её понимаешь. И чем она отличается от сишной, на основе отрывка.
edit: проверил, кстати, в сях объявление "const int x;" замечательно компилируется, x инициализируется в 0. Если объявить "extern const int x;" то ругается линкер, пока не объявишь "[const] int x = 17;" в другом файле. И печатается 17, кстати.
в плюсах на "const int x;" ругается, "extern const int x;" жрёт, линкер требует объявить x в другом файле либо без const, либо как "extern const int x = 23;", либо даже как "extern const int x; const int x = 23;"
То есть типа да, единственное отличие, но может ввести в заблуждение неподготовленного человека!

Так это, а по моему вопросу, есть у кого-нибудь идеи, почему плюсы не позволяют автоматическую инициализацию конст полей POD структур?

ava3443

Поискал по тексту стандарта C++ 2003 фразу "const-qualified", нашлось вот что:

If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or
array thereof the object shall be default-initialized; if the object is of const-qualified type, the underlying
class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for a nonstatic
object, the object and its subobjects, if any, have an indeterminate initial value); if the object or any
of its subobjects are of const-qualified type, the program is ill-formed.
не оно ли?
Кстати, все const-qualified members класса обязаны присутствовать в списке инициализации конструктора. Если отсутствует - "the program is ill-formed". Это 12.6.2, параграф 4.

bleyman

Да, это я тоже нашёл.
Смотри в чём фишка: 12.6.2 раздел посвящён инициализации конструкторами. Там из соображений производительности неинициализированные поля оказываются undefined. При этом разумеется любое неинициализированное const поле — это стопроцентный баг, потому что его никак нельзя использовать.
Тем не менее сам же 12.6.1:2 сразу ссылается на 8.5:
When an aggregate (whether class or array) contains members of class type and is initialized by a brace-enclosed initializer-list (8.5.1 each such member is copy-initialized (see 8.5) by the corresponding assignment-expression. If there are fewer initializers in the initializer-list than members of the aggregate, each member not explicitly initialized shall be value-initialized (8.5).
В таком случае ругаться на отсутвие явной инициализации конст полей неправильно, ибо там нет никакого undefined behavior, они гарантированно инициализируются нулями соответствующих типов и их можно после этого замечательно читать.
Там даже есть такое сильное утверждение: 8.5.1:8
An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T (5.2.3 where T represents the type of the uninitialized member.
(потом правда идут уточнения, что референсы всё-таки так инициализировать нельзя, но про const вроде ничего нет).
Так что либо я что-то не понимаю, либо авторы gcc и MSVC неправильно интерпретировали стандарт.
Есть ли тут у кого-нибудь ещё какие-нибудь компиляторы плюсов под рукой?

ava3443

Есть ли тут у кого-нибудь ещё какие-нибудь компиляторы плюсов под рукой?
есть, и много.
Sun Studio 12u2 на Solaris и на x86_64, и на SPARC,
HP aCC на HP-UX и на Itanium2, и на PA-RISC (под PA-RISC правда версия сейчас стоит древняя как говно мамонта)
IBM xlC на AIX
update:
раскомментировал структуру test00 и вот что вышло:
1) Sun Studio 12u2 на SPARC: скомпилил без предупреждений (были включены все предупреждения)
# CC -V
CC: Sun C++ 5.11 SunOS_sparc 2010/08/13
2) HP aCC на Itanium2 скомпилил выдав предупреждение, причём не только на test00, но и на test1.
# aCC -V
aCC: HP C/aC++ B3910B A.06.24 [Dec 04, 2009]
# aCC -g -O0 -AA vj.cpp
"vj.cpp", line 6: warning #2368-D: class "<unnamed>" defines no constructor to
initialize the following:
const member "<unnamed>::f"
struct {
^
"vj.cpp", line 16: warning #2368-D: class "<unnamed>" defines no constructor
to initialize the following:
const member "<unnamed>::f"
struct {
^
3) IBM xlC на AIX - скомпилил без предупреждений
$ xlC -qversion
IBM XL C/C++ Enterprise Edition for AIX, V9.0
Version: 09.00.0000.0002

bleyman

Ну, как проснёшься, посмотри, пожалуйста.
Если там где-нибудь всё ок, то можно будет залезть в мсфт.коннект и к гццшникам и почморить их! =)
Да, компили типа простейшую версию,

int main(int argc, char ** argv)
{
struct { const int x; } s = {};
return s.x;
}

ava3443

Результаты для простейшей версии на тех же компиляторах и платформах:
1) Sun Studio на SPARC - скомпилировал без предупреждения (если не считать argc/argv is defined but not used поле проинициализировано нулём,
2) HP aCC на HP-UX/Itanium2 - скомпилировал с предупреждением, но тоже всё работает, проинициализировано нулём.
"vj2.cpp", line 3: warning #2368-D: class "<unnamed>" defines no constructor
to initialize the following:
const member "<unnamed>::x"
struct { const int x; } s = {};
^
3) IBM xlC - ОК, скомпилил без предупреждений, проинициализировано нулём

ava3443

Проверял в gcc 3.4.6, 4.2.2.
на всякий случай проверил на последнем gcc что у меня есть - 4.4.0 в Centos 5 - по-прежнему ошибка компиляции.
$ g++44 -Wall vj2.cpp
vj2.cpp: In function ‘int main(int, char**)’:
vj2.cpp:3: error: uninitialized const member ‘main(int, char**)::<anonymous struct>::x’
Оставить комментарий
Имя или ник:
Комментарий: