Когда следует использовать возможность constexpr в C ++ 11?


337

Мне кажется, что наличие «функции, которая всегда возвращает 5» нарушает или ослабляет значение «вызова функции». Должна быть причина, или необходимость в этой возможности, иначе ее не будет в C ++ 11. Почему это там?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

Мне кажется, что если бы я написал функцию, возвращающую буквальное значение, и подошел к проверке кода, кто-то сказал бы мне, я должен был бы объявить постоянное значение вместо написания return 5.


28
Можете ли вы определить рекурсивную функцию, которая возвращает constexpr? Если так, я могу видеть использование.
ereOn

20
Я считаю, что в вопросе должно быть указано «зачем вводить новое ключевое слово (!), Если компилятор может определить для себя, может ли функция быть оценена во время компиляции или нет»). Наличие «гарантированного по ключевому слову» звучит хорошо, но я думаю, что я бы предпочел, чтобы это было гарантировано, когда это возможно, без необходимости использования ключевого слова.
Кос

6
@ Kos: Кто-то, кто БОЛЬШЕ знаком с внутренними компонентами C ++, вероятно, предпочел бы ваш вопрос, но мой вопрос возникает с точки зрения человека, который ранее писал код на C, но совсем не знаком с ключевыми словами C ++ 2011 или деталями реализации компилятора C ++ , Возможность рассуждать об оптимизации компилятора и дедукции с постоянными выражениями - это вопрос для более опытного пользователя, чем этот.
Уоррен П

8
@ Kos Я думал по тому же принципу, что и вы, и ответ, который я придумал, был, без constexpr, как бы вы (легко) узнали, что компилятор на самом деле оценил функцию во время компиляции для вас? Я полагаю, что вы могли бы проверить вывод сборки, чтобы увидеть, что она сделала, но проще сказать компилятору, что вам требуется эта оптимизация, и если по какой-то причине он не может сделать это для вас, он даст вам хороший компилятор. ошибка вместо того, чтобы молча не выполнить оптимизацию там, где вы ожидали ее оптимизировать.
Джереми Фризнер

3
@Kos: Вы могли бы сказать то же самое о const. В самом деле, уполномочено намерение это полезно ! Размеры массива являются каноническим примером.
Гонки легкости на орбите

Ответы:


303

Предположим, что-то немного сложнее.

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

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

Это в основном обеспечивает хорошую поддержку для ремонтопригодности, поскольку становится более очевидным, что вы делаете. Взять max( a, b )к примеру:

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

Это довольно простой выбор, но это означает, что если вы вызываете maxс постоянными значениями, он явно вычисляется во время компиляции, а не во время выполнения.

Другим хорошим примером будет DegreesToRadiansфункция. Каждый находит градусы легче, чем радианы. Хотя вы, возможно, знаете, что в радианах указано 180 градусов, это гораздо яснее записать следующим образом:

const float oneeighty = DegreesToRadians( 180.0f );

Много полезной информации здесь:

http://en.cppreference.com/w/cpp/language/constexpr


18
Отличный момент - он говорит компилятору попытаться вычислить значение во время компиляции. Мне любопытно, почему const не предоставляет эту функциональность, когда указаны конкретные оптимизации? Или это?
TamusJRoyce

11
@ Тамус: Часто это будет, но не обязан. constexpr обязывает компилятор и выдаст ошибку, если не сможет.
Гоз

20
Я вижу это сейчас. Грех (0,5) - это другое. Это заменяет макросы C аккуратно.
Уоррен П

10
Я вижу это как новый вопрос интервью: объясните разницу между ключевыми словами const и constexpr.
Уоррен П

2
Как способ документирования этого вопроса для себя, я написал код, аналогичный описанному выше, и снова с функцией «const», а не «constexpr». Поскольку я использую Clang3.3, -pedantic-errors и -std = c ++ 11, я ожидал, что последний не скомпилируется. Он скомпилирован и работает как в случае с «constexpr». Вы полагаете, что это расширение Clang или в спецификации C ++ 11 произошли некоторые изменения после ответа на этот пост?
Арбалет

144

Введение

constexprне был представлен как способ сказать реализации, что что-то может быть оценено в контексте, который требует константного выражения ; Соответствующие реализации смогли доказать это до C ++ 11.

То, что реализация не может доказать, является намерением определенного фрагмента кода:

  • Что разработчик хочет выразить этой сущностью?
  • Должны ли мы слепо допустить использование кода в константном выражении только потому, что это работает?

Чего бы не было в мире constexpr?

Допустим, вы разрабатываете библиотеку и понимаете, что хотите вычислить сумму каждого целого числа в интервале (0,N].

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}

Отсутствие намерения

Компилятор может легко доказать, что вышеуказанная функция вызывается в константном выражении, если переданный аргумент известен во время перевода; но вы не объявили это намерением - так уж случилось.

Теперь приходит кто-то другой, читает вашу функцию, выполняет тот же анализ, что и компилятор; « О, эту функцию можно использовать в постоянном выражении!» и пишет следующий фрагмент кода.

T arr[f(10)]; // freakin' magic

Оптимизация

Вы, как «замечательный» разработчик библиотек, решаете, что fследует кешировать результат при вызове; кто хотел бы рассчитывать один и тот же набор значений снова и снова?

int func (int n) { 
  static std::map<int, int> _cached;

  if (_cached.find (n) == _cached.end ()) 
    _cached[n] = n > 0 ? n + func (n-1) : n;

  return _cached[n];
}

Результат

Внедрив свою глупую оптимизацию, вы просто прервали каждое использование своей функции в контексте, где требовалось постоянное выражение .

Вы никогда не обещали, что функция будет использоваться в постоянном выражении , и без этого constexprне было бы способа обеспечить такое обещание.


Итак, зачем нам это constexpr?

Основное использование constexpr - объявить намерение .

Если объект не помечен как constexpr- он никогда не предназначался для использования в константном выражении ; и даже если это так, мы полагаемся на компилятор для диагностики такого контекста (потому что он игнорирует наши намерения).


25
Вероятно, это правильный ответ, поскольку недавние изменения в C ++ 14 и C ++ 17 позволяют использовать гораздо более широкий диапазон языков в constexprвыражениях. Другими словами, почти все можно аннотировать constexpr(может быть, однажды это просто исчезнет из-за этого?), И если у кого-то нет критерия, когда его использовать, constexprили нет, почти весь код будет написан как таковой. ,
Аликов

4
@alecov Определенно не все ... I/O, syscallи dynamic memory allocationопределенно не можите быть помечено как constexprКроме того, не все должно быть constexpr.
ЦзяХао Сюй

1
@alecov Некоторые функции предназначены для выполнения во время выполнения и бессмысленно делать это во время компиляции.
ЦзяХао Сюй

1
Мне также нравится этот ответ лучше всего. Оценка времени компиляции - это аккуратная оптимизация, но то, что вы действительно получаете, constexprявляется гарантией некоторого поведения. Так же, как и constделает.
Томаш Зато - Восстановить Монику

Какой компилятор позволяет эту версию без контекста? int f (int n) { return n > 0 ? n + f (n-1) : n;} T arr[f(10)]; Я не могу заставить это компилировать где-нибудь?
Иер

91

Возьмите std::numeric_limits<T>::max(): по какой-то причине, это метод. constexprбыло бы полезно здесь.

Другой пример: вы хотите объявить C-массив (или a std::array) размером с другой массив. Способ сделать это на данный момент так:

int x[10];
int y[sizeof x / sizeof x[0]];

Но не лучше ли было бы написать:

int y[size_of(x)];

Благодаря constexpr, вы можете:

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}

1
+1 для хорошего использования шаблона, но он будет работать точно так же без constexpr, не так ли?
Кос

21
@ Kos: Нет. Это возвратило бы значение времени выполнения. constexprзаставляет компилятор заставить функцию возвращать значение времени компиляции (если это возможно).
deft_code

14
@ Kos: без constexprнего его нельзя использовать ни в объявлении размера массива, ни в качестве аргумента шаблона, независимо от того, является ли результат вызова функции константой времени компиляции или нет. Эти два в основном являются единственными вариантами использования для, constexprно по крайней мере вариант использования аргумента шаблона является своего рода важным.
Конрад Рудольф

2
«по какой-то причине это метод»: причина в том, что в C ++ 03 есть только целые числа времени компиляции, но нет других типов времени компиляции, поэтому только метод может работать с произвольными типами до C ++ 11.
Себастьян Мах

5
@LwCui Нет, это не «хорошо»: GCC просто слаб по умолчанию в некоторых вещах. Используйте -pedanticопцию, и она будет помечена как ошибка.
Конрад Рудольф

19

constexprфункции действительно хороши и являются отличным дополнением к c ++. Тем не менее, вы правы в том, что большинство проблем, которые он решает, можно обойтись без макросов.

Однако одно из применений constexprне имеет C ++ 03 эквивалентных типизированных констант.

// This is bad for obvious reasons.
#define ONE 1;

// This works most of the time but isn't fully typed.
enum { TWO = 2 };

// This doesn't compile
enum { pi = 3.1415f };

// This is a file local lvalue masquerading as a global
// rvalue.  It works most of the time.  But May subtly break
// with static initialization order issues, eg pi = 0 for some files.
static const float pi = 3.1415f;

// This is a true constant rvalue
constexpr float pi = 3.1415f;

// Haven't you always wanted to do this?
// constexpr std::string awesome = "oh yeah!!!";
// UPDATE: sadly std::string lacks a constexpr ctor

struct A
{
   static const int four = 4;
   static const int five = 5;
   constexpr int six = 6;
};

int main()
{
   &A::four; // linker error
   &A::six; // compiler error

   // EXTREMELY subtle linker error
   int i = rand()? A::four: A::five;
   // It not safe use static const class variables with the ternary operator!
}

//Adding this to any cpp file would fix the linker error.
//int A::four;
//int A::six;

12
Не могли бы вы уточнить, что «ОЧЕНЬ неуловимая ошибка компоновщика»? Или хотя бы предоставить указатель на уточнение?
энобайрам

4
@enobayram, троичный оператор берет адрес операндов. Это не очевидно из кода. Все компилируется нормально, но ссылка не работает, потому что адрес fourне разрешается. Я должен был действительно копать, чтобы понять, кто берёт адрес моей static constпеременной.
deft_code

23
«Это плохо по очевидным причинам»: самая очевидная причина - точка с запятой, верно?
TonyK

4
«ЧРЕЗВЫЧАЙНО неуловимая ошибка компоновщика» меня полностью озадачило. Ни то, fourни другое five.
Стивен Лу

3
смотрите также новый enum classтип, он исправляет некоторые проблемы перечисления.
ninMonkey

14

Из того, что я прочитал, необходимость в constexpr проистекает из проблемы метапрограммирования. Классы черт могут иметь константы, представленные в виде функций, подумайте: numeric_limits :: max (). С constexpr эти типы функций могут использоваться в метапрограммировании или в качестве границ массивов и т. Д. И т. Д.

Еще один пример, который я бы выбрал, - это то, что для интерфейсов классов вы можете захотеть, чтобы производные типы определяли свои собственные константы для некоторой операции.

Редактировать:

После изучения SO, похоже, что другие придумали несколько примеров того, что может быть возможно с constexprs.


«Чтобы быть частью интерфейса, вы должны быть функцией»?
Даниэль Уорвикер

Теперь, когда я вижу полезность этого, я немного более взволнован C ++ 0x. Это кажется хорошо продуманной вещью. Я знал, что они должны быть. Эти языковые стандартные убер-гики редко делают случайные вещи.
Уоррен П

Я более взволнован лямбдами, моделью потоков, initializer_list, ссылками rvalue, шаблонами variadic, новыми перегрузками связывания ... есть много чего с нетерпением ждать.
Лука

1
О да, но я уже понимаю лямбды / затворы в нескольких других языках. constexprболее конкретно, полезен в компиляторе с мощной системой оценки выражений во время компиляции. C ++ действительно не имеет аналогов в этом домене. (это сильная похвала для C ++ 11, ИМХО)
Уоррен П

11

Из выступления Страуструпа на «Going Native 2012»:

template<int M, int K, int S> struct Unit { // a unit in the MKS system
       enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit 
struct Value {
       double val;   // the magnitude 
       explicit Value(double d) : val(d) {} // construct a Value from a double 
};

using Speed = Value<Unit<1,0,-1>>;  // meters/second type
using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
using Second = Unit<0,0,1>;  // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second 

constexpr Value<Second> operator"" s(long double d)
   // a f-p literal suffixed by ‘s’
{
  return Value<Second> (d);  
}   

constexpr Value<Second2> operator"" s2(long double d)
  // a f-p literal  suffixed by ‘s2’ 
{
  return Value<Second2> (d); 
}

Speed sp1 = 100m/9.8s; // very fast for a human 
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)  
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit) 
Acceleration acc = sp1/0.5s; // too fast for a human

2
Этот пример также можно найти в статье Страуструпа « Разработка программного обеспечения для инфраструктуры» .
Матье Полле

clang-3.3: ошибка: возвращаемый тип функции constexpr 'Value <Second>' не является литеральным типом
Mitja

Это хорошо, но кто помещает литералы в код, подобный этому. Наличие у вашего компилятора «проверки ваших единиц» для вас имеет смысл, если вы пишете интерактивный калькулятор.
Бобобобо

5
@bobobobo или, если вы писали навигационное программное обеспечение для Mars Climate Orbiter, может быть :)
Джереми Фризнер

1
Чтобы сделать его компиляцией - 1. Используйте подчеркивание в буквальных суффиксах. 2. добавить оператор "" _m для 100_m. 3. используйте 100.0_m или добавьте перегрузку, которая принимает unsigned long long. 4. Объявите конструктор Value constexpr. 5. Добавьте соответствующий оператор / в класс Value следующим образом: автоматический оператор constexpr / (const Value <Y> и другие) const {возвращаемое значение <Unit <TheUnit :: m - Value <Y> :: TheUnit :: m, TheUnit :: kg - значение <Y> :: TheUnit :: kg, TheUnit :: s - значение <Y> :: TheUnit :: s >> (val / other.val); }. Где TheUnit - это typedef для модуля, добавленного в класс Value.
0kcats

8

Другое использование (еще не упомянутое) - constexprконструкторы. Это позволяет создавать константы времени компиляции, которые не нужно инициализировать во время выполнения.

const std::complex<double> meaning_of_imagination(0, 42); 

Соедините это с пользовательскими литералами, и вы получите полную поддержку литеральных пользовательских классов.

3.14D + 42_i;

6

Раньше был шаблон с метапрограммированием:

template<unsigned T>
struct Fact {
    enum Enum {
        VALUE = Fact<T-1>*T;
    };
};

template<>
struct Fact<1u> {
    enum Enum {
        VALUE = 1;
    };
};

// Fact<10>::VALUE is known be a compile-time constant

Я полагаю, что это constexprбыло введено, чтобы позволить вам писать такие конструкции без необходимости в шаблонах и странных конструкциях со специализацией, SFINAE и прочим, но точно так же, как вы бы написали функцию времени выполнения, но с гарантией того, что результат будет определен при компиляции. -время.

Однако обратите внимание, что:

int fact(unsigned n) {
    if (n==1) return 1;
    return fact(n-1)*n;
}

int main() {
    return fact(10);
}

Скомпилируйте это с, g++ -O3и вы увидите, что fact(10)это действительно оценивается во время компиляции!

Компилятор с поддержкой VLA (например, компилятор C в режиме C99 или компилятор C ++ с расширениями C99) может даже позволить вам сделать:

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

Но то, что это нестандартный C ++ на данный момент - constexprпохоже на способ борьбы с этим (даже без VLA, в приведенном выше случае). И все еще существует проблема необходимости иметь «формальные» константные выражения в качестве аргументов шаблона.


Функция факта не оценивается во время компиляции. Это должен быть constexpr и должен иметь только один оператор return.
Sumant

1
@Sumant: Вы правы, что его не нужно оценивать во время компиляции, но это так! Я имел в виду то, что действительно происходит в компиляторах. Скомпилируйте его на недавнем GCC, посмотрите полученный asm и убедитесь, что вы мне не верите!
Кос,

Попробуйте добавить, std::array<int, fact(2)>и вы увидите, что fact () не оценивается во время компиляции. Это просто оптимизатор GCC делает хорошую работу.

1
Это то, что я сказал ... я действительно так неясен? Смотрите последний абзац
Кос

5

Только что начали переключать проект на c ++ 11 и столкнулись с совершенно хорошей ситуацией для constexpr, которая убирает альтернативные методы выполнения той же операции. Ключевым моментом здесь является то, что вы можете поместить функцию в объявление размера массива только тогда, когда она объявлена ​​constexpr. Есть ряд ситуаций, когда я вижу, что это очень полезно для продвижения вперед в той области кода, в которой я участвую.

constexpr size_t GetMaxIPV4StringLength()
{
    return ( sizeof( "255.255.255.255" ) );
}

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}

4
Это также можно записать так: const size_t MaxIPV4StringLength = sizeof ("255.255.255.255");
Superfly Jon

static inline constexpr const autoнаверное лучше.
ЦзяХао Сюй

3

Все остальные ответы великолепны, я просто хочу привести классный пример того, что вы можете сделать с constexpr, что удивительно. See-Phit ( https://github.com/rep-movsd/see-phit/blob/master/seephit.h ) - это HTML-анализатор и шаблонизатор времени компиляции. Это означает, что вы можете вставить HTML и получить дерево, которым можно манипулировать. Выполнение анализа во время компиляции может дать вам дополнительную производительность.

Из примера страницы github:

#include <iostream>
#include "seephit.h"
using namespace std;



int main()
{
  constexpr auto parser =
    R"*(
    <span >
    <p  color="red" height='10' >{{name}} is a {{profession}} in {{city}}</p  >
    </span>
    )*"_html;

  spt::tree spt_tree(parser);

  spt::template_dict dct;
  dct["name"] = "Mary";
  dct["profession"] = "doctor";
  dct["city"] = "London";

  spt_tree.root.render(cerr, dct);
  cerr << endl;

  dct["city"] = "New York";
  dct["name"] = "John";
  dct["profession"] = "janitor";

  spt_tree.root.render(cerr, dct);
  cerr << endl;
}

1

Ваш основной пример служит тем же аргументом, что и аргумент самих констант. Зачем использовать

static const int x = 5;
int arr[x];

над

int arr[5];

Потому что это намного удобнее в обслуживании. Использование constexpr намного, намного быстрее, чтобы писать и читать, чем существующие методы метапрограммирования.


0

Это может включить некоторые новые оптимизации. constТрадиционно является подсказкой для системы типов и не может использоваться для оптимизации (например, constфункция-член может const_castи в любом случае изменять объект юридически, поэтому constнельзя доверять оптимизации).

constexprозначает, что выражение действительно является константой, если входные данные для функции являются постоянными. Рассматривать:

class MyInterface {
public:
    int GetNumber() const = 0;
};

Если это раскрыто в каком-то другом модуле, компилятор не может доверять тому, что GetNumber()он не будет возвращать разные значения при каждом вызове - даже последовательно без промежуточных вызовов между ними - потому что constмог быть отброшен в реализации. (Очевидно, что любой программист, который сделал это, должен быть застрелен, но язык это позволяет, поэтому компилятор должен соблюдать правила.)

Добавление constexpr:

class MyInterface {
public:
    constexpr int GetNumber() const = 0;
};

Теперь компилятор может применить оптимизацию, когда возвращаемое значение GetNumber()кэшируется, и исключать дополнительные вызовы GetNumber(), поскольку constexprэто более надежная гарантия того, что возвращаемое значение не изменится.


На самом деле const может использоваться в оптимизации ... Это неопределенное поведение, чтобы изменить значение, определенное const, даже после const_castIIRC. Я ожидаю, что это будет согласовано с constфункциями-членами, но мне нужно проверить это со стандартом. Это будет означать, что компилятор может безопасно выполнять оптимизацию там.
Кос

1
@Warren: не имеет значения, была ли проведена оптимизация, это просто разрешено. @Kos: это малоизвестная тонкость: если исходный объект не был объявлен как const ( int xпротив const int x), его можно безопасно изменить, const_castубрав const из указателя / ссылки на него. В противном случае const_castон всегда вызывал бы неопределенное поведение и был бы бесполезен :). В этом случае компилятор не имеет информации о постоянстве исходного объекта, поэтому он не может этого сказать.
AshleysBrain

@ Kos Я не думаю, что const_cast является единственной проблемой здесь. Метод const позволяет читать и даже изменять глобальную переменную. И наоборот, кто-то из другого потока может также изменить объект const между вызовами.
энобайрам

1
«= 0» здесь недопустимо и должно быть удалено. Я бы сделал это сам, но я не уверен, что это соответствует протоколу SO.
KnowItAllWannabe

Оба примера недопустимы: первый метод ( int GetNumber() const = 0;) должен объявить GetNumber()метод виртуальным. Метод second ( constexpr int GetNumber() const = 0;) недопустим, поскольку чистый спецификатор ( = 0) подразумевает, что метод является виртуальным, но constexpr не должен быть виртуальным (ref: en.cppreference.com/w/cpp/language/constexpr )
stj

-1

Когда использовать constexpr:

  1. всякий раз, когда есть постоянная времени компиляции.

Хотя я согласен с вами, этот ответ не объясняет, почему constexpr следует отдавать предпочтение макросам препроцессора или const.
Sneftel

-3

Это полезно для чего-то вроде

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

int some_arr[MeaningOfLife()];

Свяжите это с классом черт или подобным, и это станет весьма полезным.


4
В вашем примере он предлагает нулевое преимущество по сравнению с простой константой, поэтому он не отвечает на этот вопрос.
Джалф

Это надуманный пример, представьте, что MeaningOfLife () получает свое значение откуда-то еще, скажем, из другой функции, или #define, или серии из них. Вы можете не знать, что он возвращает, это может быть код библиотеки. Другие примеры, представьте себе неизменный контейнер, который имеет метод constexpr size (). Теперь вы можете сделать int arr [container.size ()];
Пливсей

2
@plivesey, не могли бы вы отредактировать свой ответ с лучшим примером.
Мукеш,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.