Когда следует использовать возможность constexpr в C++11?
// preprocessor.
#define MEANING_OF_LIFE 42
// constants:
const int MeaningOfLife = 42;
// constexpr-function:
constexpr int MeaningOfLife () { return 42; }
13 ответов:
предположим, что он делает что-то немного сложнее.
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
с постоянными значениями он явно вычисляется во время компиляции, а не во время выполнения.еще один хороший пример будет
введение
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]; }
результат
вводя свою глупую оптимизацию, вы просто нарушили каждое использование вашей функции, которое оказалось в контексте, где константное-выражение не требуется.
вы никогда не обещали, что функция будет использоваться в a константное-выражение, а не
constexpr
не было бы никакого способа обеспечить такое обещание.
Итак, зачем нужны
constexpr
?основное использование constexpr - объявить намерение.
если объект не помечен как
constexpr
- Он никогда не предназначался для использования в константное-выражение; и даже если это так, мы полагаемся на компилятор для диагностики такого контекста (потому что он игнорирует наше намерение).
взять
std::numeric_limits<T>::max()
: по какой-то причине, это метод.constexpr
было бы полезно здесь.другой пример: вы хотите объявить с массива (или
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; }
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;
из того, что я читал, потребность в constexpr исходит из проблемы в метапрограммировании. Классы признаков могут иметь константы, представленные в виде функций, например: numeric_limits:: max(). С constexpr эти типы функций могут использоваться в метапрограммировании или в качестве границ массива и т. д.
еще одним примером с моей головы было бы то, что для интерфейсов классов вы можете захотеть, чтобы производные типы определяли свои собственные константы для некоторых операция.
Edit:
после тыкать вокруг на так, похоже, что другие придумали некоторыепримеры о том, что может быть возможно с constexprs.
из выступления Страуструп в "родных 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
другое использование (еще не упомянуто) является
constexpr
конструкторы. Это позволяет создавать константы времени компиляции,которые не должны быть инициализированы во время выполнения.const std::complex<double> meaning_of_imagination(0, 42);
соедините это с пользовательскими литералами, и у вас есть полная поддержка литеральных пользовательских классов.
3.14D + 42_i;
раньше был шаблон с метапрограммированием:
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)
is действительно evauled во время компиляции!компилятор с поддержкой VLA (так что компилятор C в режиме C99 или компилятор C++ с расширениями C99) может даже позволить вам сделать:
int main() { int tab[fact(10)]; int tab2[std::max(20,30)]; }
но что это нестандартный C++ на данный момент -
constexpr
похоже на способ борьбы с этим (даже без VLA, в приведенном выше случае). И все еще существует проблема необходимости иметь "формальные" постоянные выражения в качестве аргументов шаблона.
только что начали переключать проект на c++11 и наткнулись на совершенно хорошую ситуацию для constexpr, которая очищает альтернативные методы выполнения той же операции. Ключевым моментом здесь является то, что вы можете поместить функцию в объявление размера массива только тогда, когда она объявлена constexpr. Есть ряд ситуаций, когда я вижу, что это очень полезно продвигаться вперед с областью кода, в которой я участвую.
constexpr size_t GetMaxIPV4StringLength() { return ( sizeof( "255.255.255.255" ) ); } void SomeIPFunction() { char szIPAddress[ GetMaxIPV4StringLength() ]; SomeIPGetFunction( szIPAddress ); }
static const int x = 5; int arr[x];
over
int arr[5];
потому что это путь более ремонтопригодный. Использование constexpr намного быстрее для записи и чтения, чем существующие методы метапрограммирования.
все остальные ответы отличные, я просто хочу дать классный пример, что можно сделать с помощью constexpr, что удивительно. См. - 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; }
он может включить некоторые новые оптимизации.
const
традиционно является подсказкой для системы типов и не может использоваться для оптимизации (например, aconst
функции-члены могутconst_cast
и изменить объект в любом случае, юридически, так чтоconst
нельзя доверять для оптимизации).
constexpr
означает выражение действительно постоянная, предоставили исходные функции являются константными. Рассмотрим:class MyInterface { public: int GetNumber() const = 0; };
если это выставлено в каком-то другом модуле, то компилятор не может доверять этому
GetNumber()
не будет возвращать разные значения каждый раз, когда он вызывается-даже последовательно без неконстантных вызовов между ними-потому чтоconst
могли быть отброшены в реализации. (Очевидно, что любой программист, который сделал это, должен быть застрелен, но язык позволяет это, поэтому компилятор должен соблюдать правила.)добавлять
constexpr
:class MyInterface { public: constexpr int GetNumber() const = 0; };
компилятор теперь может применить оптимизацию, где возвращаемое значение
GetNumber()
is кэширование и устранение дополнительных вызововGetNumber()
, потому чтоconstexpr
является более сильной гарантией того, что возвращаемое значение не изменится.