Когда вы должны использовать функцию constexpr в С++ 11?
Мне кажется, что наличие "функции, которая всегда возвращает 5", ломает или разбавляет смысл "вызова функции". Должна быть причина или потребность в этой возможности, или она не будет в С++ 11. Почему он там?
// preprocessor.
#define MEANING_OF_LIFE 42
// constants:
const int MeaningOfLife = 42;
// constexpr-function:
constexpr int MeaningOfLife () { return 42; }
Мне кажется, что если бы я написал функцию, возвращающую значение буквального значения, и я подошел к обзору кода, кто-то сказал бы мне, я должен был бы объявить постоянное значение вместо записи return 5.
Ответы
Ответ 1
Предположим, что-то немного сложнее.
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
Ответ 2
Введение
constexpr
не был представлен как способ сказать реализации, что что-то можно оценить в контексте, который требует постоянного выражения; соответствующие реализации смогли доказать это до С++ 11.
То, что реализация не может доказать, - это намерение определенной части кода:
- Что разработчик хочет выразить с помощью этого объекта?
- Должны ли мы слепо разрешать использование кода в константном выражении, просто потому, что это работает?
Каким будет мир без constexpr
?
Скажем, вы разрабатываете библиотеку и понимаете, что хотите вычислить сумму каждого целого в интервале (0,N]
.
int f (int n) {
return n > 0 ? n + f (n-1) : n;
}
Отсутствие намерений
Компилятор может легко доказать, что указанная выше функция вызывается в константном выражении, если переданный аргумент известен во время перевода; но вы не заявили об этом как о намерении - это случилось именно так.
Теперь кто-то еще приходит, читает вашу функцию, делает тот же анализ, что и компилятор; "О, эта функция используется в постоянном выражении!" И записывает следующий фрагмент кода.
T arr[f(10)]; // freakin' magic
Оптимизация
Вы, как разработчик библиотеки "awesome", решили, что 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
- он никогда не предназначался для использования в константном выражении; и даже если это так, мы полагаемся на компилятор для диагностики такого контекста (поскольку он игнорирует наше намерение).
Ответ 3
Возьмите std::numeric_limits<T>::max()
: по какой-либо причине это метод. constexpr
было бы полезно здесь.
Другой пример: вы хотите объявить C-массив (или 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;
}
Ответ 4
constexpr
функции действительно приятные и отличное дополнение к С++. Тем не менее, вы правы в том, что большинство проблем, которые он решает, могут быть неэффективно обработаны с помощью макросов.
Однако одно из применений constexpr
не имеет эквивалентных С++ 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;
Ответ 5
Из того, что я читал, потребность в constexpr возникает из-за проблемы в метапрограммировании. Классы признаков могут иметь константы, представленные как функции, думайте: numeric_limits:: max(). С constexpr эти типы функций могут использоваться в метапрограммировании или в виде границ массива и т.д. И т.д.
Еще один пример от верхней части моей головы - это то, что для интерфейсов классов вы можете захотеть, чтобы производные типы определяли свои собственные константы для некоторой операции.
Edit:
После того, как он выкрикнул SO, похоже, что другие придумали некоторые примеры того, что может возможно с constexprs.
Ответ 6
Из выступления Страуструпа на "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
Ответ 7
Другое использование (еще не упомянутое) - это конструкторы constexpr
. Это позволяет создавать константы времени компиляции, которые не нужно инициализировать во время выполнения.
const std::complex<double> meaning_of_imagination(0, 42);
Сопоставьте это с определенными пользователем литералами, и у вас есть полная поддержка литеральных пользовательских классов.
3.14D + 42_i;
Ответ 8
Раньше существовал шаблон с метапрограммированием:
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 или компилятор С++ с расширениями C99) может даже позволить вам сделать:
int main() {
int tab[fact(10)];
int tab2[std::max(20,30)];
}
Но это нестандартный С++ на данный момент - constexpr
выглядит как способ борьбы с этим (даже без VLA, в приведенном выше случае). И все еще существует проблема необходимости иметь "формальные" постоянные выражения в качестве аргументов шаблона.
Ответ 9
Только что начал переключение проекта на С++ 11 и столкнулся с совершенно хорошей ситуацией для constexpr, которая очищает альтернативные методы выполнения одной и той же операции. Ключевым моментом здесь является то, что вы можете помещать функцию только в объявление размера массива, когда объявляется constexpr. Есть ряд ситуаций, когда я вижу, что это очень полезно продвигаться вперед с областью кода, в которой я участвую.
constexpr size_t GetMaxIPV4StringLength()
{
return ( sizeof( "255.255.255.255" ) );
}
void SomeIPFunction()
{
char szIPAddress[ GetMaxIPV4StringLength() ];
SomeIPGetFunction( szIPAddress );
}
Ответ 10
Все остальные ответы велики, я просто хочу привести классный пример того, что вы можете сделать с 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;
}
Ответ 11
Ваш основной пример служит тому же аргументу, что и для самих констант. Зачем использовать
static const int x = 5;
int arr[x];
над
int arr[5];
Потому что он более удобен для обслуживания. Использование constexpr намного, намного быстрее, чтобы писать и читать, чем существующие методы метапрограммирования.
Ответ 12
Он может включить некоторые новые оптимизации. const
традиционно является подсказкой для системы типов и не может использоваться для оптимизации (например, функция-член const
может const_cast
и в любом случае модифицировать объект, поэтому const
не может быть доверена для оптимизации).
constexpr
означает, что выражение действительно является постоянным, если входы в функцию const. Рассмотрим:
class MyInterface {
public:
int GetNumber() const = 0;
};
Если это отображается в каком-либо другом модуле, компилятор не может доверять тому, что GetNumber()
не будет возвращать разные значения при каждом его вызове - даже последовательно, если между ними нет неконстантных вызовов, потому что const
может иметь были отброшены в ходе реализации. (Очевидно, любой программист, который сделал это, должен быть застрелен, но язык позволяет это, поэтому компилятор должен соблюдать правила.)
Добавление constexpr
:
class MyInterface {
public:
constexpr int GetNumber() const = 0;
};
Теперь компилятор может применить оптимизацию, где возвращаемое значение GetNumber()
кэшируется и устраняет дополнительные вызовы GetNumber()
, потому что constexpr
является более сильной гарантией того, что возвращаемое значение не изменится.
Ответ 13
Когда использовать constexpr
:
- всякий раз, когда есть постоянная времени компиляции.
Ответ 14
Это полезно для чего-то вроде
// constants:
const int MeaningOfLife = 42;
// constexpr-function:
constexpr int MeaningOfLife () { return 42; }
int some_arr[MeaningOfLife()];
Свяжите это с классом признаков или т.п., и оно станет весьма полезным.