Одна из новых возможностей 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); // ОК: constexpr-переменная
	constexpr int a2 = sum (5, 12); // ошибка: функция sum не является constexp-выражением
	int a3 = new_sum (5, 12); // ОК: функция будет вызвана на этапе компиляции
	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); // ошибка: a не является constexpr-выражением, из-за чего возвращаемое значение не имеет спецификатор constexpr
}


Параметры функций не могут быть 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. Возведение в целую степень

Функции считывания числа, степени, суммы и так далее будут принимать один параметр: структуру 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.