constexpr
. With it you can create variables, functions and even objects that will be calculated at the compilation stage. This is convenient, because earlier for such purposes it was necessary to use templates. But it's not so simple. Those who are not so familiar with constexpr
may get the impression that now there will be no problems with calculations at the compilation stage. But constexpr
expressions are severely constrained.constexpr
, what will be the changes in the C ++ 14 standard, and the second part will be an example of using constexpr
: a library that considers the result of a mathematical expression in a string. constexpr auto x = "(4^2-9)/8+2/3"_solve; std::cout << "Answer is " << x;
Answer is 37/24
constexpr
words about what the constexpr
. As already mentioned, it can be used to perform some operations at the compilation stage. It looks like this: constexpr int sum (int a, int b) { return a + b; } void func() { constexpr int c = sum (5, 12); // }
constexpr _ _ ()
constexpr
added in C ++ 11 before the function means that if the parameter values can be calculated at the compilation stage, then the return value should also be counted at the compilation stage. If the value of at least one parameter is unknown at compile time, the function will be launched at runtime (and no compilation error will be displayed).constexpr = expression;
int sum (int a, int b) { return a + b; } constexpr int new_sum (int a, int b) { return a + b; } void func() { constexpr int a1 = new_sum (5, 12); // : constexpr- constexpr int a2 = sum (5, 12); // : sum constexp- int a3 = new_sum (5, 12); // : int a4 = sum (5, 12); // }
constexpr
variable is a constant ( const
), but the constant is not a constexpr variable.constexpr
-specific variable to return it back will not work, even if the value can be considered at the compilation stage. constexpr
-specifier cannot be added using const_cast, since constexpr is not a cv-specifier (this is const
and volatile
). This code will not work: constexpr int inc (int a) { return a + 1; } void func() { int a = inc (3); constexpr int b = inc (a); // : a constexpr-, - constexpr }
constexpr
. That is, it will not be possible to create an exclusively constexpr
-function that can only work at the compilation stage.constexpr
functions can work with classes, this will be discussed later.constexpr
functions, Clang also supports version 2.9, and Visual Studio 2013 does not support (but in Visual Studio "14" CTP has finally added support).constexpr
variable constraints. The constexpr variable type must be a literal type, that is, one of the following:constexpr
(but not the copy and move constructor) or no constructors at allconstexpr
variable must satisfy the following conditions:constexpr
constexpr
-variables and constexpr
-functionsvirtual
)void
cannot be returned * )static_assert
typedef
or using
that declare all types except classes and enums ( enum
)using
to specify the visibility of names or namespaces ( namespace
)return
, which can contain only literals or constexpr
-variables and constexpr
-functionsvoid
will also be a literal type.constexpr
are subject to the same restrictions as the functions, with the exception of the clause about return
and the addition of one new clause:constexpr
variables and constexpr
-functions.if-else
. On the one hand, these restrictions are made due to the fact that the compiler needs to at least somehow monitor the execution of the program during compilation (recursion is easier to interrupt than cycles). On the other hand, writing complex functions becomes problematic.if-else
, the operator “ ? :
? :
", And instead of creating variables, use the function values.constexpr
). Also, all constexpr-functions are pure functions (depend only on their parameters and return only their result). To write constexpr
algorithms, you must have at least a basic knowledge of functional programming.? :
? :
”The code is completely unreadable. Also, all this is accompanied by incomprehensible error messages that can take hundreds of lines.constexpr
function, which will then be used frequently, it would be good to return a readable error. Here you can mistakenly assume that static_assert
is suitable for this. But static_assert
cannot be used, since the parameters of the functions cannot be constexpr
, which is why the values of the parameters are not guaranteed to be known at the compilation stage. constexpr int div (int x, int y) { return (y == 0) ? throw std::logic_error ("x can't be zero") : (y / x); }
throw
construct cannot be in the constexpr function, and the function will throw an exception at runtime.throw
in the constexpr
functions. In GCC 4.8.2, this works, but in Visual Studio “14” the CTP C ++ compiler does not.void
will also be a literal type, and now it will be possible to create functions that, for example, will check the values of parameters for correctness.constexpr
class member functions are not constant. class car { constexpr int foo (int a); // C++11: const, C++14 - constexpr int foo (int a) const; };
constexpr
functions and constructors.constexpr
-functions can contain any constructions, except:goto
static
and thread_safe
variables. All variables must be initialized when defined.constexpr
should now satisfy more loyal conditions:constexpr
conditions constexpr
union
’s, but with some restrictions constexpr auto n = "(67+987^(7-3*2))*(34-123)+17^2+(-1)"_solve; std::cout << "Answer is " << n;
constexpr
variable. constexpr int operator "" _solve (const char* str, const size_t size); constexpr int solve (const char* str, const size_t size); constexpr int operator "" _solve (const char* str, const size_t size) { return solve (str, size); }
#define math_assert(condition,description) ((condition) ? 0 : (throw std::logic_error (description), 0))
SMathData
. A string is stored in it, its size and the start
variable - where to start reading: struct SMathData { constexpr SMathData (const char* _str, const int _size, const int _start) : str (_str), size (_size), start (_start) {} constexpr SMathData create (const int _start) const { return SMathData (str, size, _start); } constexpr char char_start() const { return char_at (start); } constexpr char char_at (const int pos) const { return (pos >= 0 && pos < size) ? str[pos] : ((pos == size) ? 0 : (math_assert (false, "Internal error: out of bounds"), 0)); } const char* str; const int size; const int start; };
SMathValue
. It stores the calculated value and end
is a variable in which the end of the number, sum, product or something else is written: struct SMathValue { constexpr SMathValue (const int _value, const int _end) : value (_value), end (_end) {} constexpr SMathValue add_end (int dend) const { return SMathValue (value, end + dend); } const int value; const int end; };
// ( ). constexpr SMathValue get_number (const SMathData data); // ( ). // positive == true, , false - . i - . constexpr SMathValue _get_number (const SMathData data, const int i, const bool positive); // (start - ). constexpr int _get_number_end (const SMathData data); constexpr SMathValue get_number (const SMathData data) { return (data.char_start() == '-') ? (math_assert (data.char_at (data.start + 1) >= '0' && data.char_at (data.start + 1) <= '9', "Not a digit"), _get_number (data.create (data.start + 1), _get_number_end (data.create (data.start + 1)), false)) : (math_assert (data.char_start() >= '0' && data.char_start() <= '9', "Digit required"), _get_number (data, _get_number_end (data), true)); } constexpr SMathValue _get_number (const SMathData data, const int i, const bool positive) { return (i >= data.start) ? SMathValue (_get_number (data, i - 1, positive).value * 10 + (positive ? 1 : -1) * (data.char_at (i) - '0'), i) : SMathValue (0, data.start - 1); } constexpr int _get_number_end (const SMathData data) { return (data.char_start() >= '0' && data.char_start() <= '9') ? _get_number_end (data.create (data.start + 1)) : (data.start - 1); }
get_number
checks that the current index is actually a number and calls _get_number
, passing the end of the number as the first iteration (the number is read from right to left). // get branum - get bracket or number. constexpr SMathValue get_branum (const SMathData data); constexpr SMathValue get_branum (const SMathData data) { return (data.char_start() == '(') ? (math_assert (data.char_at (get_addsub (data.create (data.start + 1)).end + 1) == ')', "')' required"), get_addsub (data.create (data.start + 1)).add_end (1)) : get_number (data); }
get_number
; otherwise, the function counts the expression in brackets. // . constexpr SMathValue get_pow (const SMathData data); // . , start ( ), // '^', . value - ( ). constexpr SMathValue _get_pow (const SMathData data, const int value); constexpr SMathValue get_pow (const SMathData data) { return _get_pow (data.create (get_branum (data).end + 1), get_branum (data).value); } constexpr SMathValue _get_pow (const SMathData data, const int value) { return (data.char_start() == '^') ? _get_pow (data.create // start (get_branum (data.create (data.start + 1)).end + 1), // value math_pow (value, get_branum (data.create (data.start + 1)).value)) : SMathValue (value, data.start - 1); }
_get_pow
function checks that the current character is '^'
. If this is the case, the function calls itself (or rather, get_pow
), passing there a new value equal to value
in the degree read_value."25"
correctly processed if get_pow
called for it. Since in this case the number is simply read, after which it will return.math_pow
is a simple constexpr
function. constexpr int math_pow (const int x, const int y); constexpr int _math_pow (const int x, const int y, const int value); constexpr int math_pow (const int x, const int y) { return math_assert (y >= 0, "Power can't be negative"), _math_pow (x, y.to_int(), 1); } constexpr int _math_pow (const int x, const int y, const int value) { return (y == 0) ? value : (x * _math_pow (x, y - 1, value)); }
// . constexpr SMathValue get_muldiv (const SMathData data); // . _get_pow. constexpr SMathValue _get_muldiv (const SMathData data, const int value); constexpr SMathValue get_muldiv (const SMathData data) { return _get_muldiv (data.create (get_pow (data).end + 1), get_pow (data).value); } constexpr SMathValue _get_muldiv (const SMathData data, const int value) { return (data.char_start() == '*') ? _get_muldiv (data.create // start (get_pow (data.create (data.start + 1)).end + 1), // value value * get_pow (data.create (data.start + 1)).value) : ((data.char_start() == '/') ? (get_pow (data.create (data.start + 1)).value == 0) ? math_assert (false, "Division by zero") : _get_muldiv (data.create // start (get_pow (data.create (data.start + 1)).end + 1), // value value / get_pow (data.create (data.start + 1)).value) : SMathValue (value, data.start - 1)); }
'*'
, if so, then the function calls itself, multiplying value
by the read number (or expression). In the case of '/'
function behaves similarly, only before this there is a check that the denominator is not zero. If the current character is not '*'
or '/'
, then the value is simply returned. constexpr SMathValue get_addsub (const SMathData data); constexpr SMathValue _get_addsub (const SMathData data, const CMathVariable value); constexpr SMathValue get_addsub (const SMathData data) { return _get_addsub (data.create (get_muldiv (data).end + 1), get_muldiv (data).value); } constexpr SMathValue _get_addsub (const SMathData data, const CMathVariable value) { return (data.char_start() == '+') ? _get_addsub (data.create // start (get_muldiv (data.create (data.start + 1)).end + 1), // value value + get_muldiv (data.create (data.start + 1)).value) : ((data.char_start() == '-') ? _get_addsub (data.create // start (get_muldiv (data.create (data.start + 1)).end + 1), // value value - get_muldiv (data.create (data.start + 1)).value) : SMathValue (value, data.start - 1)); }
get_addsub
and _get_addsub
similar to the functions get_muldiv
and _getmuldiv
respectively.solve
function: constexpr CMathVariable solve (const char* str, const size_t size); // get_value , // ( , value.end == size), . constexpr int get_value (const int size, const SMathValue value); constexpr int solve (const char* str, const size_t size) { return get_value (static_cast<int> (size), get_addsub (SMathData (str, static_cast<int> (size), 0))); } constexpr int get_value (const int size, const SMathValue value) { return math_assert (value.end + 1 == size, "Digit or operator required"), value.value; }
constexpr
. class CMathVariable { private: int64_t numerator_; uint64_t denominator_; constexpr CMathVariable (int64_t numerator, uint64_t denominator); constexpr int64_t sign_ (int64_t a) const; constexpr uint64_t gcd_ (uint64_t a, uint64_t b) const; constexpr CMathVariable reduce_() const; public: constexpr explicit CMathVariable (int number); constexpr CMathVariable operator + (const CMathVariable& n) const; constexpr CMathVariable operator - (const CMathVariable& n) const; constexpr CMathVariable operator * (const CMathVariable& n) const; constexpr CMathVariable operator / (const CMathVariable& n) const; constexpr int64_t numerator() const; constexpr uint64_t denominator() const; constexpr bool is_plus_inf() const; constexpr bool is_menus_inf() const; constexpr bool is_nan() const; constexpr bool is_inf() const; constexpr bool is_usual() const; constexpr bool is_integer() const; constexpr int to_int() const; constexpr int force_to_int() const; constexpr double to_double() const; friend constexpr CMathVariable operator - (const CMathVariable& n); friend constexpr CMathVariable operator + (const CMathVariable& n); friend std::ostream& operator << (std::ostream& os, const CMathVariable& var); }; constexpr CMathVariable operator - (const CMathVariable& n); constexpr CMathVariable operator + (const CMathVariable& n); std::ostream& operator << (std::ostream& os, const CMathVariable& var); constexpr CMathVariable::CMathVariable (int number) : numerator_ (number), denominator_ (1) { } constexpr CMathVariable::CMathVariable (int64_t numerator, uint64_t denominator) : numerator_ (numerator), denominator_ (denominator) { } constexpr int64_t CMathVariable::sign_ (int64_t a) const { return (a > 0) - (a < 0); } constexpr uint64_t CMathVariable::gcd_ (uint64_t a, uint64_t b) const { return (b == 0) ? a : gcd_ (b, a % b); } constexpr CMathVariable CMathVariable::reduce_() const { return (numerator_ == 0) ? CMathVariable (0, sign_ (denominator_)) : ((denominator_ == 0) ? CMathVariable (sign_ (numerator_), 0) : CMathVariable (numerator_ / gcd_ (static_cast<uint64_t> (std::abs (numerator_)), denominator_), denominator_ / gcd_ (static_cast<uint64_t> (std::abs (numerator_)), denominator_))); } constexpr int64_t CMathVariable::numerator() const { return numerator_; } constexpr uint64_t CMathVariable::denominator() const { return denominator_; } constexpr bool CMathVariable::is_plus_inf() const { return denominator_ == 0 && numerator_ > 0; } constexpr bool CMathVariable::is_menus_inf() const { return denominator_ == 0 && numerator_ < 0; } constexpr bool CMathVariable::is_nan() const { return denominator_ == 0 && numerator_ == 0; } constexpr bool CMathVariable::is_inf() const { return denominator_ == 0 && numerator_ != 0; } constexpr bool CMathVariable::is_usual() const { return denominator_ != 0; } constexpr bool CMathVariable::is_integer() const { return denominator_ == 1; } constexpr int CMathVariable::to_int() const { return static_cast<int> (numerator_ / denominator_); } constexpr int CMathVariable::force_to_int() const { return (!(denominator_ == 1 && static_cast<int> (numerator_) == numerator_) ? (throw std::logic_error ("CMathVariable can't be represented by int"), 0) : 0), to_int(); } constexpr double CMathVariable::to_double() const { return static_cast<double> (numerator_) / denominator_; } constexpr CMathVariable CMathVariable::operator + (const CMathVariable& n) const { return CMathVariable ( static_cast<int64_t> (n.denominator_ / gcd_ (denominator_, n.denominator_)) * numerator_ + static_cast<int64_t> (denominator_ / gcd_ (denominator_, n.denominator_)) * n.numerator_, denominator_ / gcd_ (denominator_, n.denominator_) * n.denominator_).reduce_(); } constexpr CMathVariable CMathVariable::operator - (const CMathVariable& n) const { return CMathVariable ( static_cast<int64_t> (n.denominator_ / gcd_ (denominator_, n.denominator_)) * numerator_ - static_cast<int64_t> (denominator_ / gcd_ (denominator_, n.denominator_)) * n.numerator_, denominator_ / gcd_ (denominator_, n.denominator_) * n.denominator_).reduce_(); } constexpr CMathVariable CMathVariable::operator * (const CMathVariable& n) const { return CMathVariable ( numerator_ * n.numerator_, denominator_ * n.denominator_).reduce_(); } constexpr CMathVariable CMathVariable::operator / (const CMathVariable& n) const { return CMathVariable ( numerator_ * static_cast<int64_t> (n.denominator_) * (n.numerator_ ? sign_ (n.numerator_) : 1), denominator_ * static_cast<uint64_t> (std::abs (n.numerator_))).reduce_(); } constexpr CMathVariable operator + (const CMathVariable& n) { return n; } constexpr CMathVariable operator - (const CMathVariable& n) { return CMathVariable (-n.numerator_, n.denominator_); } std::ostream& operator << (std::ostream& stream, const CMathVariable& var) { if (var.is_plus_inf()) stream << "+inf"; else if (var.is_menus_inf()) stream << "-inf"; else if (var.is_nan()) stream << "nan"; else if (var.denominator() == 1) stream << var.numerator(); else stream << var.numerator() << " / " << var.denominator(); return stream; }
Source: https://habr.com/ru/post/228181/
All Articles