«constexpr» функции не имеют спецификатор «const»

Original author: Andrzej Krzemieński
  • Translation
Просто хотел Вас предупредить: С++14 не будет обратно совместим с C++11 в одном аспекте constexpr функций.

В С++11, если Вы определите constexpr функцию-член, то она неявно получит спецификатор const:
// C++11
struct NonNegative
{
  int i;
  constexpr int const& get() /*const*/ { return i; }
  int& get() { return i; }
};

Первое объявление функции get получит спецификатор const, даже если мы не укажем это явно. Следовательно, эти две функции являются перегруженными: const и не-const версии.

В С++14 это будет уже не так: оба объявления будут определять одиннаковую, не-const версию функции-члена с различающимися возвращаемыми значениями — это приведет к ошибке компиляции. Если Вы уже начали использовать constexpr функции и надеетесь на неявный спецификатор const, то я советую Вам начать добавлять его явно, чтобы Ваш код продолжал компилироваться, если Вы решите перейти на компиляторы С++14.

Что не так с неявным const?


Проблемы начнутся, если Вы попытаетесь использовать наш тип следующим образом:
// C++11
constexpr int i = NonNegative{2}.get(); // ERROR

Согласно (несколько необычным) правилам С++, при выборе функции-члена для временного объекта, не-const версия предпочтительней const версии. Наша не-const функция-член get не является constexpr, поэтому она не может быть использована для инициализации constexpr переменной и мы получим ошибку на этапе компиляции. Мы не можем сделать эту функцию constexpr, потому что это автоматически добавит спецификатор const

Я сказал, что правила подбора лучшей функции — необычны, потому что они немного противоречат тому, как мы выбираем лучшую функцию-нечлен для временных объектов. В этом случае мы предпочитаем const lvalue ссылки к не-const версии:
// C++11
constexpr int const& get(NonNegative const& n) { return n.i; }
constexpr int& get(NonNegative& n) { return n.i; }
 
NonNegative N = readValue();
constexpr int * P = &get(N);
 
int main() 
{ 
  *P = 1;
}

Смотрите, что получается: глобальная переменная N не является константой. Поэтому вторая, не-const перегруженная функция выбирается для вызова при инициализации указателя P. Но не-const функция при этом все равно имеет спецификатор constexpr! А все потому, что правило "constexpr означает const" применяется только для неявного this аргумента нестатической функции-члена. constexpr функция может получить ссылку на не-const объект и вернуть ссылку на не-const подобъект. Здесь нету никаких проблем: адрес глобального объекта постоянен и известен во время компиляции. Однако значение по адресу P не постоянно и может быть изменено позже.

Если предыдущий пример выглядит несколько надуманным, рассмотрим следующий, более реалистичный пример:
// C++11
constexpr NonNegative* address(NonNegative& n) { return &n; } 
 
NonNegative n{0}; // non-const
constexpr NonNegative* p = address(n);

Здесь все работает отлично, но если Вы попытаетесь сделать address функцией членом, она перестанет работать:
// C++11
struct NonNegative
{
  // ...
  constexpr NonNegative* maddress() { return this; } // ERROR
};
 
NonNegative n{0}; // non-const
constexpr NonNegative* p = n.maddress();

Это потому, что maddress неявно определен со спецификатором const, this имеет тип NonNegative const* и не может быть конвертировано к NonNegative*.

Следует отметить, что это не сама функция-член является const, а неявный (this) аргумент функции. Объявление функции-члена может быть переписано в псевдо-коде как:
// PSEUDO CODE
struct NonNegative
{
  // ...
  constexpr NonNegative* maddress(NonNegative const& (*this)); 
};

И этот неявный аргумент функции, в отличие от других аргументов функций, получает (иногда нежелательный) спецификатор const.

Эта асимметричность будет удалена в С++14. Если Вы хотите спецификатор const для неявного аргумента (this), Вам следует добавить его самим. Следующий код будет действительным в C++14:
// C++14
struct NonNegative
{
  int i;
  constexpr int const& get() const { return i; }
  constexpr int& get() { return i; }
};

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 26

    –3
    Выглядит несколько костыльно в итоге. А что тогда насчёт static constexpr?
      0
      А что с ним особенного? К нему не передается неявный this, поэтому такие функции ведут себя так же, как и функции-нечлены.
        +1
        Ее, вроде, даже const пометить нельзя.
          0
          А так ли прямо нужен этот указатель this в constexpr методах, разве от него можно что-то интересное узнать кроме constexpr полей класса, которые также наверное лучше сразу помечать как static?
            0
            class A {
                int m_a;
            public:
                constexpr A(int a) : m_a(a) {}
                constexpr int a() const { return m_a; }
            };
            
            constexpr int fib(int v) { ... }
            
            int main() {
                A a1(1);
                A a2(2);
                int fib1 = fib(a1.a());
                int fib2 = fib(a2.a());
            }
            

            И fib1, и fib2 инициализированы на стадии компиляции. У каждого класса свои данные, их не нужно помечать constexpr, чтобы провести инициализацию во время компиляции, и static тут вообще не причем.
              0
              Хм а вот про constexpr конструкторы не слышал) Тогда для них это все имеет смысл.
                0
                Прошу прощения, в данном случае объекты должны быть созданы следующим образом:
                    constexpr A a1(1);
                    constexpr A a2(2);
                

                Хотя суть от этого не сильно меняется.
        +2
        Больше 10 лет работали над стандартом, а теперь такие хотфиксы. Ох не зря я на другом языке сейчас пишу.
          +2
          Да, подобные хотфиксы это неприятно, но расскажите мне, о могучий, где вы нашли живой язык, где нет таких хотфиксов или еще что по хуже.
            0
            Больше всего не люблю аргументы в духе «а в Африке дети голодают», «а у других ещё хуже». Более 10 лет, понимаете? За это время язык D, которому приходится тяжело без поддержки крупных корпораций, уже достиг уровня юзабельности, и о нём недавно состоялась конференция DConf 2013, видео по которой выложил — кто бы вы думали — Андрей Александреску, известный адепт и мастер C++, а также активный разработчик самого языка D и его стандартной библиотеки:
            www.youtube.com/channel/UCzYzlIaxNosNLAueoQaQYXw

            А C++ за это время просто стал менее уродливым, сохранив, тем не менее, одну из своих раздражающих особенностей — сложность разработки компиляторов и парсеров для него. В результате для одного и самых популярных языков планеты есть всего пара нормальных IDE, не говоря уж о хороших (как IntelliJ Idea для Java). И это я ещё говорю о PC, а в embedded до недавнего времени была вообще полная задница — что ни IDE, то Notepad с подсветкой синтаксиса + свой убогий компилятор; только недавно начали осиливать сборку и настройку Eclipse. Теперь в embedded компиляторы, по-прежнему — самописное дерьмо, часто поддерживающее C++ вполсилы, зато каждая IDE — Eclipse с плагинами.

            Ну, а слово «хотфикс» по отношению к сроку в 3 года я бы расценивал как сарказм.
              –3
              М-да, реакция адептов других языков и хейтеров всегда предсказуема в своей неадекватности. В поте выше выстроена связь. «Переходить на другой язык, т.к. есть хотфиксы» ну или что-то типа того. Собственно, мое замечание и говорит, что хотфиксы есть у все, на какой язык вы не переходили бы.

              Собственно в этом и весь смысл. И не пытайтесь найти тут холиварную тему о недостатка и достоиствая С++. Я всего лишь призываю быть адекватными.
                0
                Я не адепт, т.к. на пишу на тех языках, на которых нужно (на работе) — например, Java, а не на тех, на которых хочется — например, Scheme (с последними балуюсь дома в свободное время). И уж тем более не хейтер, потому что пишу на C++ уже лет 8, и за это время всего лишь устал от его кривизны, которая все эти годы заставляет изворачиваться так, как не приходится в более продуманных и современных языках.

                Вы теряете суть комментария, на который изначально отвечали: сроки в более 10 лет для разработки стандарта просто смехотворны в 21 веке и неприемлемы в условиях нынешних темпов развития технологий, равно как и сроки в несколько лет для исправления таких стандартов. За такое время корпорации умудряются разрабатывать не просто языки, а целые платформы — например, .NET (предвосхищая необоснованные обвинения, считаю нужным уточнить, что не являюсь фанатом Microsoft).

                Я к тому, что… «где вы нашли живой язык, где нет таких хотфиксов» — вот таких хотфиксов нигде больше нет, это точно. А чё, времени у всех много, люди живут долго — можно и ещё 20 лет подождать, пока язык наконец догонит остальные по юзабилити, и пока во всех компиляторах реализуют новые фичи. Если у вас завалялся лишний бутылёк эликсира бессмертия, будьте другом — поделитесь. А я в 2014 году планирую заняться доработкой компиляторов интересных мне языков. А через несколько лет посмотрим, где будет C++, а где — всё остальное.
                  0
                  Значит так, я уже сказал, что не буду участвовать в холиваре на тему С. Т.к. как и в любом холиваре здесь мало радости и мало правды и все жутко субъективно.

                  Посему отвечаю только на: «Вы теряете суть комментария » и на «И уж тем более не хейтер».

                  Итак: «больше 10 лет работали над стандартом» — все стандарты С++: создание 1983, первый стандарт 1998, далее 2003, затем 2011. Т.е. перерыва в разработке стандарта в 10 лет и более нет. Т.е налицо передергивание(особенно, если учесть, что разработка стандарта велась с 2009). но как бы то ни было, это декларация ожиданий
                  «а теперь такие хотфиксы» -противопоставление с ожиданием. Далее должен быть вывод ибо сама конструкция
                  «декларация ожидания» — «противопоставление с реальностью» это подразумевает и без выводов она бессмысленна и тян6ет на высказыавние КО.
                  " Ох не зря я на другом языке сейчас пишу." собственно вывод. А с учетом передергивания это собственно это только подчеркивается

                  Теперь насчет того хейтер. Собственно последняя фраза является доказательством и квинтесенцией эмоционального абзаца " А я в 2014 году планирую заняться доработкой компиляторов интересных мне языков. А через несколько лет посмотрим, где будет C++, а где — всё остальное." Вы противопоставили себя всем программистам, пишущим на С++ и бросили вызов. Но вас никто не вызывал и не говорил, что вы криворук и не умеете писать на С++ или что-то подобное. Я не хчу выступать в роли психолога или гуру, но просто ответьте себе сами зачем вы это написали.
                  .
                    0
                    Не то, чтобы я противопоставлял себя всем плюсовикам. Вы меня как-то не так поняли. Я хотел сказать, что с такими темпами развития C++ растеряет популярность, а я за это время успею освоить одну из самых сложных для меня вещей в computer science. По крайней мере, веб C++ мог бы захватить, став более комфортным для использования, учитывая постоянно растущую необходимость в оптимизации обработки запросов. Скриптовые языки пока что гораздо удобнее, но имеют потолок производительности, да и JIT в случае многократного запуска скрипта (в противовес запуску функции) бесполезен. C++ есть, куда расти, и этот рост следовало бы ускорить, чтобы не только оставаться на плаву, но и расширять сферу влияния.
                      0
                      Вот как раз на сервере JIT полезнее, чем на клиенте. На клиенте JIT должен отработать быстро, иначе приложение будет запускаться полчаса. А на сервере у JIT все приемущества.
                      — Он точно знает, на какой архитектуре запущено приложение и может оптимизировать код конкретно под нее.
                      — Он обладает статистикой использования кода и может автопрофилироваться в зависимости от характера нагрузки.

                      Зато развертывание приложений, компилирующихся в нативный код в принципе не может быть настолько простым, насколько это возможно в случае JIT. В нативном приложении нельзя на ходу поменять часть кода, в нативном приложении всю оптимизацию необходимо сделать в момент компиляции. Именно поэтому Java и завоевала большую популярность на серверной стороне, в то время как интерфейсы на Java, тот же самый Eclipse — жуткие тормоза.
                        0
                        C++ есть, куда расти, и этот рост следовало бы ускорить, чтобы не только оставаться на плаву, но и расширять сферу влияния.

                        Ну с этим никто не спорит.
                        Я хотел сказать, что с такими темпами развития C++ растеряет популярность, а я за это время успею освоить одну из самых сложных для меня вещей в computer science

                        Вот это высказывание уже гораздо адекватнее. Тогда Вам встречный вопрос: а почему не хотите заняться доработкой Clang?
                        Стандарт С++1y, только в процессе, а они уже готовятся к нему: clang.llvm.org/cxx_status.html, к слову, и у GCC здесь все неплохо: gcc.gnu.org/projects/cxx1y.html
                          0
                          Потому что C++ всё ещё остаётся одним из самых сложных языков с точки зрения разработчиков компиляторов. Учиться на именно на C++ для меня будет сложновато. Портировать, к примеру, Racket на эти компиляторы — задача бессмысленная и объёмная — проще дорабатывать его VM и JIT-компилятор, а D и так уже почти официально в GCC (GDC). В общем, начну с чего-нибудь попроще.
                  0
                  У С++ просто большое сообщество, и все хотят разного — поэтому и стандарты долго утрясаются. Это пока про ваш язык знает 4 человека, можно принять новый стандарт между первой и второй бутылками пива на фанвстрече:)

                  А что с компилляторами не так? Есть же gcc, который поддерживает кучу платформ.
                    0
                    Попробуйте использовать GCC в самописной IDE для парсинга C++ и реализации автодополнения — получите бесценный опыт. Со временем компиляции кода на C++ тоже всё не очень хорошо.
                      0
                      При чем тут IDE? Я про gcc говорил в контексте компиллятора под embedded.
                        0
                        И как там с поддержкой MSP430 и C++11?
                          0
                          Так вот тут же есть gcc 4.6.3. У него уже все более-менее с C++11.
                            0
                            Когда я пытался использовать это три месяца назад, msp430-gdb падал при попытке загрузить elf на целевой МК:

                            target remote :9999
                            file blablabla.elf
                            load

                            Если уже починили, тогда да, всё замечательно. Не знаю, правда, когда представится возможность проверить, т.к. после проекта сети датчиков на CC430 я окончательно убедился в том, что лучше использовать STM32 + nRF.
              0
              > Согласно (несколько необычным) правилам С++, при выборе функции-члена для временного объекта, не-const версия предпочтительней const версии

              А под этим есть хоть какая-то логика? Просто это выгляди очень странно, когда мы передаём временную переменную в функцию по ссылке, ссылка обязательно должна быть const, иначе кидается ошибка компиляции:

              coliru.stacked-crooked.com/a/6cfbe273f9871aaa
                0
                Я бы не сказал, что это утверждение о предпочтении не-const версии верное. Правило простое: для константных объектов вызывается const-метод, для неконстантных объектов вызываются не-const метод. Временное значение хоть и является rvalue, оно не является константным объектом.

                В случае же передачи временного объекта ссылка на константу обусловлена другим правилом: rvalue может быть привязано только к ссылке на константу.

                Т.е. странность тут всё же есть: не привызывая можно менять объект, но если привязать, то менять его нельзя. Но и смысл присутствует, это нужно для того, чтобы подобный код работал:

                    SomeClass().method();
                

                Иначе пришлось бы создавать дополнительные переменные в таких случаях.
                  0
                  Спасибо! Теперь этот аспект и противоречия (которых на самом деле нет — я понял где мой фэйл) разложены в моей голове по полочкам.

              Only users with full accounts can post comments. Log in, please.