Специфікатор constexpr в C + +11 і в C + +14

    Одна з нових можливостей C + +11 — специфікатор
constexpr
. За допомогою нього можна створювати змінні, функції і навіть об'єкти, які будуть розраховані на етапі компіляції. Це зручно, адже раніше для таких цілей доводилося використовувати шаблони. Але тут все не так просто. У тих, хто не так добре знайомий з
constexpr
, може скластися враження, що тепер не буде жодних проблем з розрахунками на етапі компіляції. Але на
constexpr
-вирази накладені серйозні обмеження.
 
У першій частині буде розказано про
constexpr
, про те, які будуть зміни в стандарті C + +14, а у другій частині буде приклад використання
constexpr
: бібліотека, яка вважає результат математичного виразу в рядку.
За допомогою неї можна буде написати наступний код:
 
constexpr auto x = "(4^2-9)/8+2/3"_solve;
std::cout << "Answer is " << x;

І відповідь у вигляді дробу буде отриманий на етапі компіляції:
 
Answer is 37/24

Відразу попереджаю, код цієї бібліотеки складно зрозуміти.
Кому ця тема цікава, ласкаво просимо під кат!
 
 Що таке constexpr?
Спочатку пара слів про те, що взагалі таке специфікатор
constexpr
. Як вже було сказано, за допомогою нього можна виробляти якісь операції на етапі компіляції. Виглядає це так:
 
constexpr int sum (int a, int b)
{
	return a + b;
}

void func()
{
	constexpr int c = sum (5, 12); // значение переменной будет посчитано на этапе компиляции
}

 

constexpr-функція

 
constexpr возвращаемое_значение имя_функции (параметры)

Ключове слово
constexpr
, доданий в C + +11, перед функцією означає, що якщо значення параметрів можливо порахувати на етапі компіляції, то повертається значення також має порахуватися на етапі компіляції. Якщо значення хоча б одного параметра буде невідомо на етапі компіляції, то функція буде запущена в runtime (а не буде виведена помилка компіляції).
 
 

constexpr-змінна

 
constexpr тип = expression;

Ключове слово в даному випадку означає створення константи. Причому 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); // OK: constexpr-переменная
	constexpr int a2 = sum (5, 12); // ошибка: преобразование int -> constexpr int
	int a3 = new_sum (5, 12); // ОК: неявное преобразование constexpr int -> int
	int a4 = sum (5, 12); // ОК
}

 
 
constexpr
-змінна є константою (
const
), але константа не є constexpr-змінної.
 
У випадку «втрати»
constexpr
-специфікатора змінної повернути назад його вже не вийде, навіть якщо значення може порахуватися на етапі компіляції.
constexpr
-специфікатор не можна додати за допомогою const_cast, так як constexpr не є cv-специфікатором (це
const
і
volatile
). Такий код не запрацює:
 
constexpr int inc (int a)
{
	return a + 1;
}

void func()
{
	int a = inc (3);
	constexpr int b = inc (a); // ошибка: преобразование int -> constexpr int
	constexpr int c = inc (const_cast<constexpr int> (a)); // ошибка: const_cast не может преобразовать int -> constexpr int
}

 
Параметри функцій не можуть бути
constexpr
. Тобто не вийде створити виключно
constexpr
-функцію, яка може працювати тільки на етапі компіляції.
 
Також
constexpr
-функції можуть працювати з об'єктами, це буде розглянуто пізніше.
 
GCC, починаючи з версії 4.4, підтримує
constexpr
-функції, Clang також підтримує з версії 2.9, а Visual Studio 2013 не підтримує (але в Visual Studio «14» CTP нарешті додали підтримку).
 
 Обмеження
Тепер, коли ви зрозуміли, як це все зручно, можна додати ложку дьогтю в бочку меду. Причому досить велику ложку.
 
Почнемо з обмежень
constexpr
-змінних. Тип constexpr-змінної повинен бути Літеральна типом, тобто одним з наступних:
 
     
  • Скалярний тип
  •  
  • Покажчик
  •  
  • Масив скалярних типів
  •  
  • Клас, який задовольняє таким умовам:
      
       
    • Має деструктор за замовчуванням
    •  
    • Всі нестатичні члени класу мають бути літеральнимі типами

    •  
    • Клас повинен мати хоча б один
      constexpr
      -конструктор (але не конструктор копіювання і переміщення) або не мати конструкторів зовсім
    •  
      
  •  
 
constexpr
-змінна повинна задовольняти таким умовам:
 
     
  • Її тип повинен бути Літеральна
  •  
  • Їй має бути відразу присвоєно значення або викликаний
    constexpr
    -конструктор
  •  
  • Параметри конструктора або присвоєне значення можуть містити тільки літерали або
    constexpr
    -змінні і
    constexpr
    -функції
  •  
Тут начебто нічого незвичайного. Основні обмеження накладені на constexpr-функції:
 
     
  • Вона не може бути віртуальної (
    virtual
    )
  •  
  • Вона повинна повертати літеральний тип (
    void
    повернути не можна * )
  •  
  • Всі параметри повинні мати літеральний тип
  •  
  • Тіло функції повинно містити тільки наступне:
      
       
    • static_assert

    •  
    • typedef
      або
      using
      , які оголошують всі типи, крім класів і перерахувань (
      enum
      )
    •  
    • using 
      для вказівки видимості імен або просторів імен (
      namespace
      )
    •  
    • Рівне один
      return
      , який може містити тільки літерали або
      constexpr
      -змінні і
      constexpr
      -функції
    •  
      
  •  
 * З C + +14
void
також буде Літеральна типом.

 
На
constexpr
-конструктори накладені такі ж обмеження, як і на функції, за винятком пункту про
return
і з додаванням одного нового пункту:
Всі нестатичні члени класу і члени базових класів повинні бути ініціалізовані-яким чином (в конструкторі, використовуючи списки ініцілізаціі або ініцілізаціей членів класу при оголошенні), причому присвоєні їм вирази повинні містити тільки літерали або
constexpr
-змінні і
constexpr
-функції.
 
Виходить, що у функціях не можна ініціалізувати змінні, створювати цикли і конструкції
if-else
. З одного боку, ці обмеження зроблені через те, що компілятору потрібно хоч якось відстежувати виконання програми під час компіляції (рекурсию простіше переривати, ніж цикли). З іншого — писати складні функції стає проблематично.
 
Звичайно, все одно всі ці можливості можна реалізувати. Замість циклів використовувати рекурсію, замість конструкції
if-else
— оператор «
? :
», а замість створення змінних використовувати значення функції.
 
Все це сильно нагадує функціональне програмування. У функціональних мовах програмування, як правило, також не можна заводити змінні і відсутні цикли. Дійсно, функції викликати можна, функції вищих порядків теж можна реалізувати, використовуючи покажчики на функції (на жаль, анонімні функції (лямбда) не можна використовувати в
constexpr
-конструкціях). Також всі constexpr-функції є чистими функціями (залежать тільки від своїх параметрів і повертають тільки свій результат). Щоб писати
constexpr
-алгоритми, потрібно мати хоча б початкові знання функціонального програмування.
 
Але тут у C + + великі проблеми з синтаксисом: анонімні функції не можна використовувати, всі дії функції є одним довгим виразом, а з додаванням оператора «
? :
» код зовсім стає нечитабельним. Також все це супроводжується незрозумілими повідомленнями про помилку, які можуть займати сотні рядків.
 
Але на цьому проблеми не закінчуються. Коли пишеш якусь
constexpr
-функцію, яку потім будуть часто використовувати, добре б повертати читабельну помилку. Тут можна помилково припустити, що
static_assert
якраз для цього підходить. Але
static_assert
використовувати не вийде, оскільки параметри функцій не можуть бути
constexpr
, через що значення параметрів не гарантував будуть відомі на етапі компіляції.
Як же виводити помилки? Єдиний більш-менш нормальний спосіб, який я знайшов, полягає в викиданні винятки:
 
constexpr int div (int x, int y)
{
	return (y == 0) ? throw std::logic_error ("x can't be zero") : (y / x);
}

У разі виклику функції під час компіляції ми побачимо помилку, що конструкція
throw
не може перебувати в constexpr-функції, а в runtime функція викине виняток.
Помилку складно буде знайти, але хоч щось.
 Приклад помилки в gcc 4.8.2 Main.cpp: 16:24: in constexpr expansion of 'MathCpp :: operator "" _solve (((const char *) "(67 +987 ^ (7-3 * 2)) * (34-123) +17 ^ 2/0 + (-1)"), 37ul) '
MathCpp.h: 115:28: in constexpr expansion of 'MathCpp :: solve (str, ((size_t) size))'
MathCpp.h: 120:103: in constexpr expansion of 'MathCpp :: get_addsub (MathCpp :: SMathData (str, ((int) size), 0))'
MathCpp.h: 209:89: in constexpr expansion of ‘MathCpp::_get_addsub(data.MathCpp::SMathData::create((((int)MathCpp::get_muldiv(data).MathCpp::SMathValue::end) + 1)), MathCpp :: get_muldiv (data). MathCpp :: SMathValue :: value) '
MathCpp.h: 217:50: in constexpr expansion of 'MathCpp :: get_muldiv (data.MathCpp :: SMathData :: create ((((int) data.MathCpp :: SMathData :: start) + 1)))'
MathCpp.h: 181:83: in constexpr expansion of ‘MathCpp::_get_muldiv(data.MathCpp::SMathData::create((((int)MathCpp::get_pow(data).MathCpp::SMathValue::end) + 1)), MathCpp :: get_pow (data). MathCpp :: SMathValue :: value) '
MathCpp.h: 38:111: error: expression '<throw-expression>' is not a constant-expression
 # Define math_assert (condition, description) ((condition)? INVALID_VALUE: (throw std :: logic_error (description), INVALID_VALUE))
 ^
MathCpp.h: 195:15: note: in expansion of macro 'math_assert'
? math_assert (false, «Division by zero»)
 
Такий спосіб виведення помилки ще не відповідає стандарту мови, нічого не забороняє компілятору завжди видавати помилку про те, що не можна використовувати
throw
в
constexpr
-функції. У GCC 4.8.2 це працює, а в Visual Studio «14» CTP C + + compiler — вже ні.
 
У підсумку складно писати, складно налагоджувати, складно розуміти такі конструкції.
Але все не так погано, в C + +14 дуже багато обмежень приберуть.
 
 Зміни в C + +14
Як вже було сказано, в новому стандарті
void
також буде Літеральна типом, і тепер можна буде створювати функції, які, наприклад, перевірятимуть значення параметрів на правильність.
 
Друге незначна зміна полягає в тому, що тепер
constexpr
функції-члени класу не є константними.
У C + +11 наступні рядки були рівносильними, а в С + +14 це вже не так:
 
class car
{
	constexpr int foo (int a); // C++11: функция неявно получает спецификатор const, C++14 - не получает
	constexpr int foo (int a) const;
};

Пояснення цьому можна знайти тут .
 
І нарешті, головна зміна дозволяє використовувати майже будь-які конструкції в
constexpr
функціях і конструкторах.
Тепер тіло
constexpr
-функції може містити будь-які конструкції, крім:
 
     
  • ассемблерного вставок
  •  
  • Ключового слова
    goto
  •  
  • Визначення змінних нелітерального типу або
    static
    і
    thread_safe
    -змінних. Всі змінні повинні инициализироваться при визначенні.
  •  
 
А тіло
constexpr
-конструктора тепер має задовольняти більш лояльним умовам:
 
     
  • Він повинен відповідати всім умовам
    constexpr
    -функції
  •  
  • Всі його нестатичні члени повинні мати літеральний тип
  •  
  • Аналогічна умова про те, що всі нестатичні члени класу повинні будь-яким способом инициализироваться
  •  
  • З'явилася можливість використовувати
    union
    'и, але з деякими обмеженнями
  •  
 
У підсумку після появи компіляторів, які підтримують C + +14, можна буде писати constexpr-функції, які майже нічим не відрізнятимуться від звичайних. А поки що доводиться писати досить заплутаний код.
 
 Приклад використання constexpr на C + +11
Як приклад використання constexpr буде приведена бібліотека, яка буде вважати результат математичного виразу, що знаходиться в рядку.
 
Отже, ми хочемо, щоб можна було писати такий код:
 
constexpr auto n = "(67+987^(7-3*2))*(34-123)+17^2+(-1)"_solve;
std::cout << "Answer is " << n;

Тут використовується ще одна нова можливість C + +11: користувальницькі літерали. У даному випадку вони хороші тим, що функція гарантовано буде викликана на етапі компіляції, навіть якщо вийшло значення буде присвоєно НЕ
constexpr
-змінної.
 
Оголошується користувальницький літерал таким чином:
 
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))

Бібліотека може складати, віднімати, множити, ділити, зводити в ступінь, також є підтримка дужок. Реалізовано це буде за допомогою рекурсивного спуску.
 
Пріоритети операторів будуть такі (від більш високих до більш низьких):
 
     
  1. Додавання і віднімання
  2.  
  3. Множення і ділення
  4.  
  5. Піднесення до цілого степеня
  6.  
Функції зчитування числа, ступеня, суми і так далі будуть приймати один параметр: структуру
SMathData
. У ній зберігаються рядок, її розмір і змінна
start
— звідки треба починати читати:
 
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
. У ній зберігаються пораховані значення і
end
— змінна, в яку записаний кінець числа, суми, твори або чогось ще:
 
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;
};

 
Для зчитування числа будуть 3 функції (одна основна і дві допоміжних):
 
// Считывает число (поддерживается унарный минус).
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
перевіряє, що на поточному індексі дійсно число і викликає
_get_number
, передаючи в якості першої ітерації кінець числа (число читається справа наліво).
 
Робота з дужками:
 
// 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
, інакше функція вважає вираз в дужках.
 
Далі йде функція зведення в ступінь:
 
// Возвращает значение после возведения в степень.
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
перевіряється, що поточний символ
'^'
. Якщо це так, то функція викликає сама себе (точніше
get_pow
), передавши туди нове значення, рівне
value
в ступені прочітанное_значеніе.
 
Виходить, що рядок
"25"
правильно обробиться, якщо для неї викликати
get_pow
. Так як в цьому випадку просто прочитається число, після чого воно повернеться.
 
math_pow
— проста
constexpr
-функція зведення в цілу ступінь.
 Реалізація math_pow
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));
}

Досить складно зрозуміти цю конструкцію, писати її також важко. Тут йде перевірка, чи є поточний символ
'*'
, якщо це так, то функція викликає сама себе, перемножая
value
на прочитане число (або вираз). У випадку з
'/'
функція веде себе аналогічно, тільки перед цим йде перевірка на те, що знаменник не дорівнює нулю. Якщо поточний символ не є
'*'
або
'/'
, то просто повертається значення.
 
Аналогічно відбувається з сумою і різницею:
 Реалізація get_addsub
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
і
_get_addsub
аналогічна роботі функцій
get_muldiv
і
_getmuldiv
відповідно.
 
І нарешті, залишилося реалізувати функцію
solve
:
 
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;
}

 
Після цього треба трохи змінити код рекурсивного спуску і в результаті отримати потрібне.
Написання рекурсивного спуску на constexpr-функціях зайняло десь день, хоча звичайний рекурсивний спуск без проблем пишеться за годину. Проблеми були з плутаниною з дужками, зі складністю налагодження, з незрозумілими помилками, з непродуманістю архітектури (так, тепер навіть для рекурсивного спуску треба ретельно все продумувати).
 
Репозиторій з цією бібліотекою знаходиться тут: https://bitbucket.org/jjeka/mathcpp
 
Якщо є якісь недоліки або питання, пишіть!
 P.S. Вважаю, що вже пора б створювати хаб, присвячений C + +11 / +14.
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.