Форум программистов, компьютерный форум CyberForum.ru

Как компилятор обрабатывает член класса static constexpr const char* - C++

Восстановить пароль Регистрация
 
Kastaneda
Модератор
Эксперт С++
 Аватар для Kastaneda
4248 / 2780 / 219
Регистрация: 12.12.2009
Сообщений: 7,109
Записей в блоге: 1
Завершенные тесты: 1
16.09.2016, 12:05     Как компилятор обрабатывает член класса static constexpr const char* #1
Привет!

Наткнулся на непонятный момент
C++
1
2
3
4
5
class Foo
{
public:
    static constexpr const char* PTR = "value";
};
без constexpr не компилируется.
Вопрос - как компилятора без линкера разруливает это дело, ведь constexpr value ему нужен на этапе компиляции? Если он это пережевывает, почему тогда constexpr необходим?
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
gray_fox
What a waste!
 Аватар для gray_fox
1246 / 1129 / 54
Регистрация: 21.04.2012
Сообщений: 2,354
Завершенные тесты: 3
16.09.2016, 22:28     Как компилятор обрабатывает член класса static constexpr const char* #2
Kastaneda, судя по сообщениям компилятора, const char* - не "интегральный" тип, и видимо в этом загвоздка - совмещать объявление статического члена класса и его инициализацию можно только для "интегральных" типов; в то время как при использовании constexpr инициализация необходима при объявлении.
DrOffset
6786 / 3997 / 917
Регистрация: 30.01.2014
Сообщений: 6,816
17.09.2016, 00:17     Как компилятор обрабатывает член класса static constexpr const char* #3
Цитата Сообщение от Kastaneda Посмотреть сообщение
Вопрос - как компилятора без линкера разруливает это дело, ведь constexpr value ему нужен на этапе компиляции? Если он это пережевывает, почему тогда constexpr необходим?
Если понадобится адрес переменной, то потребуется определение (без него будет ошибка линкера). До этих пор линкер вообще не задействуется - это compile-time константа.
Kastaneda
Модератор
Эксперт С++
 Аватар для Kastaneda
4248 / 2780 / 219
Регистрация: 12.12.2009
Сообщений: 7,109
Записей в блоге: 1
Завершенные тесты: 1
17.09.2016, 05:25  [ТС]     Как компилятор обрабатывает член класса static constexpr const char* #4
Цитата Сообщение от DrOffset Посмотреть сообщение
До этих пор линкер вообще не задействуется - это compile-time константа.
да, это я понимаю. Но ведь эта константа содержит адрес строки, который становится известным на этапе линковки, как компилятор обходится без линкера?
ct0r
C++/Haskell
 Аватар для ct0r
1559 / 578 / 39
Регистрация: 19.08.2012
Сообщений: 1,194
Завершенные тесты: 1
17.09.2016, 14:33     Как компилятор обрабатывает член класса static constexpr const char* #5
Цитата Сообщение от Kastaneda Посмотреть сообщение
Но ведь эта константа содержит адрес строки, который становится известным на этапе линковки, как компилятор обходится без линкера?
1. Эта штука называется address constant expression.
2. А зачем ему линкер? Для таких объектов у компилятора адрес - это просто какая-то релокация. Он ее потом отправит в elf и все. А линкер дальше будет с ее использованием по соответствующему выражению адрес считать. Компилятору же нужно только знать, что эта релокация - константа.
DrOffset
6786 / 3997 / 917
Регистрация: 30.01.2014
Сообщений: 6,816
17.09.2016, 18:16     Как компилятор обрабатывает член класса static constexpr const char* #6
Цитата Сообщение от Kastaneda Посмотреть сообщение
как компилятор обходится без линкера?
Точно также, как он обходится без него, когда ты передаешь адрес функции в параметр шаблона.
Kastaneda
Модератор
Эксперт С++
 Аватар для Kastaneda
4248 / 2780 / 219
Регистрация: 12.12.2009
Сообщений: 7,109
Записей в блоге: 1
Завершенные тесты: 1
18.09.2016, 08:45  [ТС]     Как компилятор обрабатывает член класса static constexpr const char* #7
Цитата Сообщение от ct0r Посмотреть сообщение
Эта штука называется address constant expression
Во, спасибо, знаний вот этого термина мне не хватало.

Цитата Сообщение от DrOffset Посмотреть сообщение
Точно также, как он обходится без него, когда ты передаешь адрес функции в параметр шаблона.
Да, логично, похоже разобрались
TheCalligrapher
С чаем беда...
Эксперт С++
 Аватар для TheCalligrapher
3127 / 1567 / 423
Регистрация: 18.10.2014
Сообщений: 2,903
20.09.2016, 06:14     Как компилятор обрабатывает член класса static constexpr const char* #8
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Цитата Сообщение от Kastaneda Посмотреть сообщение
Вопрос - как компилятора без линкера разруливает это дело, ведь constexpr value ему нужен на этапе компиляции? Если он это пережевывает, почему тогда constexpr необходим?
Концепция адресной константы была еще в языке С с самого начала стандартизованных времен. Про этом язык С никогда не поддерживал динамической инициализации для объектов со статическим storage duration. Т.е. при старте программы на C все объекты со статическим storage duration должны "родиться" уже проинициализированными, в том числе такие как

C
1
2
static int i;
static int *pi = &i;
Понятно, что значение pi не может быть известным на стадии компиляции. Более того, это значение даже в те времена в Unix не обязательно было известно даже на стадии линковки, а могло оказаться известным только на стадии загрузки (например, для глобальных объектов, определенных в shared objects). То есть конкретное значение адресной константы в общем случае может быть даже не линкером, а только загрузчиком в момент начала выполнения.

По этой причине спецификация адресных констант в С была специально подогнана под возможности загрузчиков. А именно, в С адресные константы выступают как константы только в инициализаторах и только либо сами собой, либо с прибавлением к адресной константе (или вычитанием из нее) какого-то смещения, известного на стадии компиляции (что разрешает в т.ч. применение операторов [] и -> в комбинации с & для формирования новых адресных констант). Другими словами, адресные константы в С могут быть использованы как константы только для целей относительной адресации.

То есть в рамках спецификации, нельзя написать

C
1
static int array[(size_t) pi % 100];
ибо, понятое дело, это требует знания точного значения (size_t) pi на стадии компиляции.

Так вот спецификация constexpr в С++ совсем не далеко уходит от этих ограничений, пришедших в С++ из С. Фактически, все, чего вы добиваетесь указанием constexpr в этом объявлении указателя, это превращение в constexpr таких выражений, как PTR[5]. Для этого инициализатор PTR должен быть виден компилятору, т.е. присутствовать в объявлении. Ясно, что для определения значения PTR[5] в таком случае компилятору совсем не нужно знать конкретного значения PTR.

Любые же попытки "выковырять" из PTR его численное значение на стадии компиляции (или как-то еще завязаться на абсолютную точку в памяти, куда он указывает) обречены на провал, даже несмотря на то, что PTR объявлен как constexpr.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16935 / 5340 / 328
Регистрация: 30.03.2009
Сообщений: 14,354
Записей в блоге: 26
20.09.2016, 09:26     Как компилятор обрабатывает член класса static constexpr const char* #9
Если вернуться к изначальному вопросу. Почему без constexpr не компилируется - уже ответили: инициализатор должен быть не в теле класса, а там, где объявлено само тело статического поля. Т.е. где-то вне тела класса должно быть написано

C++
const char* Foo::PTR = "value";
Из объяснений TheCalligrapher я так понял, что для статической инициализации указателя constexpr ничего принципиально нового не вносит (во всяком случае на низком уровне). А почему наличие constexpr позволяет инициализатор писать в теле класса? На всякий случай: мне непонятна только языковая часть, как работает компилятор, линкер, загрузчик и прочее на низком уровне, мне хорошо известно
TheCalligrapher
С чаем беда...
Эксперт С++
 Аватар для TheCalligrapher
3127 / 1567 / 423
Регистрация: 18.10.2014
Сообщений: 2,903
20.09.2016, 09:46     Как компилятор обрабатывает член класса static constexpr const char* #10
Цитата Сообщение от Evg Посмотреть сообщение
А почему наличие constexpr позволяет инициализатор писать в теле класса?
Для всех static constexpr разрешается писать инициализатор прямо в теле класса. Это необходимо для того, чтобы реализовать вычисления на стадии компиляции. В вычислениях на стадии компиляции и заключается вся идея constexpr.

В данном случае инициализатор необходим для того, чтобы на стадии компиляции вычислять выражения вида PTR[5].

При этом определением указателя PTR такое объявление не становится, даже несмотря на наличие инициализатора. Если где-то в коде PTR окажется использованным как lvalue, то придется его еще где-то определить. Инициализатор в определении повторять не надо.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16935 / 5340 / 328
Регистрация: 30.03.2009
Сообщений: 14,354
Записей в блоге: 26
20.09.2016, 15:24     Как компилятор обрабатывает член класса static constexpr const char* #11
Я правильно понимаю ситуацию? Сначала (условно в C++98) разрешили писать инициализатор в теле класса ради того, чтобы static-поле класса можно было, грубо говоря, использовать в качестве размерности НЕавтоматического массива. Для указателей подобной необходимости не было, а потому не разрешали. Теперь (с C++11) из-за constexpr появилась необходимость использовать указатель (как rvalue) в других constexpr выражениях в НЕавтоматических местах, растущих от других constexpr'ов. Типа того?
TheCalligrapher
С чаем беда...
Эксперт С++
 Аватар для TheCalligrapher
3127 / 1567 / 423
Регистрация: 18.10.2014
Сообщений: 2,903
21.09.2016, 08:34     Как компилятор обрабатывает член класса static constexpr const char* #12
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Цитата Сообщение от Evg Посмотреть сообщение
Я правильно понимаю ситуацию? Сначала (условно в C++98) разрешили писать инициализатор в теле класса ради того, чтобы static-поле класса можно было, грубо говоря, использовать в качестве размерности НЕавтоматического массива. Для указателей подобной необходимости не было, а потому не разрешали. Теперь (с C++11) из-за constexpr появилась необходимость использовать указатель (как rvalue) в других constexpr выражениях в НЕавтоматических местах, растущих от других constexpr'ов. Типа того?
Ну да. Типа того.

В С++98 (и С++03) возможность использования static const членов класса в качестве констант времени компиляции понимали (отсюда разрешение на указание инициализаторов для целых и enum типов), но это возможность все равно рассматривалась, как "вторичная". Например, правила ODR в С++98 формально требовали отдельного определения такого члена класса, если он использовался (used) в программе. При этом не придавалось никакого значения тому, как он использовался. Даже если static const использовался только как rvalue (напр. только для задания размера массива), все равно наличие определения безусловно требовалось спецификацией языка. Хотя понятно, что с точки зрения здравого смысла такое определение было никому не нужно. (Многие компиляторы натуральным образом не следили за соблюдением этого требования, если такая константа использовалась только как rvalue).

В С++11 возможность использования таких констант именно и только как констант времени компиляции уже вышла на первый план. Правила ODR были модифицированы и требуют отдельного определения static const членов класса только если они odr-используются (odr-used) в программе, т.е. если они где-то используются именно как lvalue. А если такая константа используется только как rvalue, то делать для нее отдельное определение больше не требуется.

Но дальше расширять функциональность static const не стали (возможно, там скрыты какие-то конфликты), т.е. указывать инициализаторы для static const прямо в классе по-прежнему можно только для целых и enum типов. Вместо этого для создания констант времени компиляции ввели совершенно новый механизм constexpr. Для constexpr констант указывать инициализаторы прямо в классе можно всегда, независимо от типа. Понятно, что наличие видимых отовсюду инициализаторов совершенно необходимо для того, чтобы обрабатывать такие константы на стадии компиляции.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16935 / 5340 / 328
Регистрация: 30.03.2009
Сообщений: 14,354
Записей в блоге: 26
21.09.2016, 16:12     Как компилятор обрабатывает член класса static constexpr const char* #13
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Даже если static const использовался только как rvalue (напр. только для задания размера массива), все равно наличие определения безусловно требовалось спецификацией языка. Хотя понятно, что с точки зрения здравого смысла такое определение было никому не нужно. (Многие компиляторы натуральным образом не следили за соблюдением этого требования, если такая константа использовалась только как rvalue)
Тут проблема немного другого уровня. Компилятор в общем-то не в состоянии проконтролировать, что определение неиспользуемой как value переменной где-то присутствует. Т.е. когда переменная используется (например, на неё взяли адрес), а определения нигде нет, то оно сломается на линковке. А когда переменная нигде не используется (все использования были заменены на константы), то очень сложно предпринимать усилия для того, чтобы сломаться в том случае, если определение переменной ни в одном из модулей не присутствует. Только путём построения избыточного кода или каких-то читерских вещей
Kastaneda
Модератор
Эксперт С++
 Аватар для Kastaneda
4248 / 2780 / 219
Регистрация: 12.12.2009
Сообщений: 7,109
Записей в блоге: 1
Завершенные тесты: 1
22.09.2016, 06:54  [ТС]     Как компилятор обрабатывает член класса static constexpr const char* #14
После этого
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Фактически, все, чего вы добиваетесь указанием constexpr в этом объявлении указателя, это превращение в constexpr таких выражений, как PTR[5]. Для этого инициализатор PTR должен быть виден компилятору, т.е. присутствовать в объявлении. Ясно, что для определения значения PTR[5] в таком случае компилятору совсем не нужно знать конкретного значения PTR
окончательно все встало на свои места, спасибо.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
22.09.2016, 22:19     Как компилятор обрабатывает член класса static constexpr const char*
Еще ссылки по теме:

Static член класса C++
C++ Чем отличаются объявления const char* и const* char
Копировать пустую строку в const char* Конструктор структуры (класса) C++
Инициализация в объявлении константных статических (const static) членов-данных класса. C++
Static-член класса C++

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
TheCalligrapher
С чаем беда...
Эксперт С++
 Аватар для TheCalligrapher
3127 / 1567 / 423
Регистрация: 18.10.2014
Сообщений: 2,903
22.09.2016, 22:19     Как компилятор обрабатывает член класса static constexpr const char* #15
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Если где-то в коде PTR окажется использованным как lvalue, то придется его еще где-то определить. Инициализатор в определении повторять не надо.
Тут, кстати, стоит добавить, что в С++17 все сильно меняется.

В С++17 вводится понятие inline variable. И можно будет писать так

C++
1
2
3
4
struct S
{
  inline static int x = 42;
}
при этом вышеприведенное объявление переменной S::x является ее определением. Переменную можно использовать как lvalue, и при этом какого-то дополнительного определения для нее делать не надо (и нельзя).

Далее, constexpr константы в С++17 становятся частным случаем inline variable. При этом, для сохранения обратной совместимости, писать отдельное "определение" для такой константы разрешается, но является deprecated

C++
1
2
3
4
5
6
struct S
{
  static constexpr int x = 42; // теперь это - определение
};
 
constexpr int S::x; // пока можно, но deprecated
Yandex
Объявления
22.09.2016, 22:19     Как компилятор обрабатывает член класса static constexpr const char*
Ответ Создать тему
Опции темы

Текущее время: 11:45. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru