Как стать автором
Обновить

Комментарии 64

Прежде чем выйдет следующая статья — однопоточный растеризатор, основанный на half-space (в этом коммите), типично раза в 4 раза медленнее растеризатора на сканлайнах — за счёт оверхеда по bounding box и трех счетчиков. Чтобы извлечь приличную выгоду из half-space, нужно разбивать экран на блоки и распараллеливать отрисовку по блокам, как это делается в GPU. Выглядит он, конечно, просто, но на практике такой простой код никуда не годится.
Секунду, <<на практике>> нас вообще не интересует, это вынесено в место, про которое отдельно сказано, что для нас это чёрный ящик. Чем проще код, тем лучше. Разумеется, в реальной жизни это всё запрограммировано даже не прошивкой, а тупо кремнием. Но это не суть данного курса. Данный курс нацелен на то, чтобы показать, что все вычисления за красивыми картинками тривиальны и доступны любому (заинтересованному) школьнику.
>> Чтобы извлечь приличную выгоду из half-space, нужно разбивать экран на блоки и распараллеливать отрисовку по блокам, как это делается в GPU.

Интересная статья про софтверную растеризацию в Larrabee.
software.intel.com/en-us/articles/rasterization-on-larrabee
НЛО прилетело и опубликовало эту надпись здесь
Я использовал стандартный контейнер <h4> для заголовков верхнего уровня (4, 5, 6), и <h5> для заголовков вложенного (5.1, 5.2, ..., 5.7) уровня. Перед публикацией я посмотрел статью на «обычном» Firefox и Chrome, а также на Firefox с темой HabraDarkAges и не заметил, что текст визуально «рассыпается». В последнем случае, заголовки даже были выделены отдельным цветом, что на мой взгляд, удобно.
Пожалейте же читателей кода! Зачем лепить километровые предложения template<...> на одну строку с самим типом/функцией? Ну проще же читать, когда можно разделить эти сущности визуально.
Я внес поправки и надеюсь, теперь шаблоны действительно стали выглядеть лучше.
Есть два способа это сделать:
Справедливости ради, есть ещё третий способ: специализировать шаблон mat для квадратных матриц и добавить в эту специализация характерные только для таких матриц методы.
Здесь снова проблема: в эту частичную специализацию придется также поместить индексные операторы, хранение самой матрицы, генератор единичной матрицы и так далее. Удвоим объем кода: получим два места, в которых фактически делается одно и тоже.
Можно написать базовый класс с общими операциям и унаследовать оба варианта от него.
При наследовании шаблона от шаблона появятся дикие конструкции вроде this->rows[i][j]. Обсуждение этого феномена на stackoverflow
Я по поводу нахождения обратной матрицы. Можно обращать, используя теорему Гамильтона-Кэли. В этом случае нужно найти всего лишь n степеней матрицы и следы полученных матриц. Гораздо быстрее будет работать для больших матриц, чем вычисления с использованием союзной матрицы. Есть ещё метод Крылова.
Но ведь в самом начале текста сказано, что нас интересуют только маленькие плотнозаполненные матрицы…
Думаю, что стоит попробовать сравнить алгоритмы на матрицах малого порядка.
Было бы интересно, можете написать код?
Попробую, только не тестировал (возможно, косякнул, прошу извинить). Сейчас кину ссылку на книгу, где описан метод. Вот код.

mat<DimRows,DimCols,number_t> invert_transpose(const mat<DimRows,DimCols,number_t>& A)
{
	mat<DimRows,DimCols,number_t> PowMat[DimRows+1];

	// Вычисляем степени матриц
	PowMat[0] = identity();
	PowMat[1] = A;
	for (size_t i=2; i <= DimRows; i++)
	{
		PowMat[i] = PowMat[i-1] * A;
	}

	number_t s[DimRows], p[DimRows];
	for (size_t i = 0; i < DimRows; i++)
	{
		// Вычисляем следы матриц (суммы диагональных элементов)
		s[i] = 0;
		for(size_t j = DimRows; j--; )
			s[i] += PowMat[i+1][j][j];

		// Определяем коэффициенты характеристического уравнения матрицы A
		p[i] = s[i];
		for(size_t j = 1; j < i; j++)
			p[i] -= p[j-1] * s[i-j];
		p[i] /= i + 1;
	}

	// Рассчитываем обратную матрицу по следствию из теоремы Гамильтона-Кэли
	mat<DimRows,DimCols,number_t> result = PowMat[DimRows - 1];
	for(size_t i = 0; i < DimRows - 1; i++)
		result -= p[i] * PowMat[DimRows - i - 2];
	return result/p[DimRows - 1];
}
Здесь тип number_t предполагает либо double, либо long double, либо класс с перегруженными арифметическими операциями (например, mpreal).
Код работать не желает.
Исходная матрица:
                                  [ 1  4  9 ]
                                  [         ]
(%o4)                             [ 2  9  7 ]
                                  [         ]
                                  [ 4  3  8 ]


Решение при помощи maxima:
(%i3) float(transpose(invert(matrix([1,4,9],[2,9,7],[4,3,8]))));
      [ - 0.2982456140350877  - 0.07017543859649122    0.1754385964912281   ]
      [                                                                     ]
(%o3) [ 0.02923976608187134    0.1637426900584795    - 0.07602339181286549  ]
      [                                                                     ]
      [  0.3099415204678362   - 0.06432748538011696  - 0.005847953216374269 ]

Решение нашим методом с союзной матрицей:
-0.298246 -0.0701754 0.175439 
0.0292398 0.163743 -0.0760234 
0.309942 -0.0643275 -0.00584795 

Решение вашим методом:
0.352381 0.015873 0.168254 
-0.0380952 0.603175 -0.0349206 
0.0952381 -0.0412698 0.511111 

Потребовалось доопределить отсутствующие у нас операторы - умножение матрицы на скаляр и вычетание матриц:
template<size_t DimRows,size_t DimCols,typename T> 
    mat<DimRows,DimRows,T> operator-(
                                   const mat<DimRows,DimCols,T>& lhs, 
                                   const mat<DimRows,DimCols,T>& rhs
                       )
{
    mat<DimRows,DimCols,T> ret;
    for(size_t i=DimCols;i--;)
    {
        ret[i]=lhs[i]-rhs[i];
    }
    return(ret);
}
template<size_t DimRows,size_t DimCols,typename T> mat<DimCols,DimRows,T>
        operator*(
                        mat<DimRows,DimCols,T> lhs, 
                        const T& rhs
                      ) 
{
    for (size_t i=DimRows; i--;)
    {
        lhs[i]=lhs[i]*rhs;
    }
    return lhs;
}
Я ещё раз сейчас проверю. Книга Воеводиных «Параллельные вычисления», стр. 204-205 + книга Гатмахера.
У меня есть подозрение, что если просто сосчитать количество арифметических операций, потребных для применения этого метода, и сравнить с количеством операций, необходимых для метода с союзной матрицей — разница будет в пользу метода с союзной матрицей.

Кроме того, в этом учебнике бойко сообщают оценки числа потребных процессоров, но ничего не говорят об оценках потребности на межпроцессную коммуникацию: пересылку данных и синхронизацию. Это довольно странно.
Согласен.
*Гантмахера.
Нашёл первую ошибку.

mat<DimRows,DimCols,number_t> invert_transpose(const mat<DimRows,DimCols,number_t>& A)
{
    mat<DimRows,DimCols,number_t> PowMat[DimRows+1];

    // Вычисляем степени матрицы
    PowMat[0] = identity();
    PowMat[1] = A;
    for (size_t i=2; i <= DimRows; i++)
    {
        PowMat[i] = PowMat[i-1] * A;
    }

    number_t s[DimRows], p[DimRows];
    for (size_t i = 0; i < DimRows; i++)
    {
        // Вычисляем следы матриц (суммы диагональных элементов)
        s[i] = 0;
        for(size_t j = DimRows; j--; )
            s[i] += PowMat[i+1][j][j];

        // Определяем коэффициенты характеристического уравнения матрицы A
        p[i] = s[i];
        for(size_t j = 1; j <= i; j++)
            p[i] -= p[j-1] * s[i-j];
        p[i] /= i + 1;
    }

    // Рассчитываем обратную матрицу по следствию из теоремы Гамильтона-Кэли
    mat<DimRows,DimCols,number_t> result = PowMat[DimRows - 1];
    for(size_t i = 0; i < DimRows - 1; i++)
        result -= p[i] * PowMat[DimRows - i - 2];
    return result/p[DimRows - 1];
}

Учитывается равенство во внутреннем цикле. Попробуйте.
Сошлось, поздравляю (учтите, что метод с союзной матрицей выдает транспонированное решение)!
////////С союзной матрицей:
-0.298246 -0.0701754 0.175439 
0.0292398 0.163743 -0.0760234 
0.309942 -0.0643275 -0.00584795 

////////По теореме Кэли-Гамильтона
-0.298246 0.0292398 0.309942 
-0.0701754 0.163743 -0.0643275 
0.175439 -0.0760234 -0.00584795 

Кстати тот факт, что метод с союзной матрицей «бесплатно» еще и транспонирует оказался нам на руку — с том месте, где нам нужно обращать матрицу, нам как раз нужен транспонированный вариант.
Не совсем, некоторые элементы получаются с ошибкой. А если потестировать ещё?
Все одинаково, просто одну из матриц нужно транспонировать для сравнения
Всё понял, спасибо.
Я вроде каждое с каждым сравнил (числа совпадают), сильно путает «транспонированность».

Я сейчас задал сильно перекошенную матрицу (и транспонировал результат метода с союзной матрицей, чтобы глаза не ломать)
Это вариант с float
Source
          2.00000000           4.00000000           9.00000000           2.00000000 
          2.00000000           9.00000000           7.00000000      100000.00000000 
          4.00000000           3.00000000           8.00000000           0.00000100 
       4000.00000000        6633.00000000      995522.00000000           1.13000000 

With Cofactors
         -0.29927686           0.00000599           0.40014544          -0.00000051 
          0.40298930          -0.00000806          -0.19945022          -0.00000204 
         -0.00148256           0.00000003          -0.00027888           0.00000102 
         -0.00003018           0.00001000           0.00000997           0.00000000 

With Cayley–Hamilton theorem
         -0.30498824           0.00000610           0.40778178          -0.00000052 
          0.41067991          -0.00000824          -0.20325670          -0.00000872 
         -0.00151085           0.00000003          -0.00028420           0.00000104 
         -0.00003080           0.00001036           0.00000000           0.00000000 


Это вариант с double
Source
          2.00000000           4.00000000           9.00000000           2.00000000 
          2.00000000           9.00000000           7.00000000      100000.00000000 
          4.00000000           3.00000000           8.00000000           0.00000100 
       4000.00000000        6633.00000000      995522.00000000           1.13000000 

With Cofactors
         -0.29927683           0.00000599           0.40014542          -0.00000051 
          0.40298926          -0.00000806          -0.19945022          -0.00000204 
         -0.00148256           0.00000003          -0.00027888           0.00000102 
         -0.00003018           0.00001000           0.00000997           0.00000000 

With Cayley–Hamilton theorem
         -0.29927683           0.00000599           0.40014542          -0.00000051 
          0.40298926          -0.00000806          -0.19945022          -0.00000204 
         -0.00148256           0.00000003          -0.00027888           0.00000102 
         -0.00003018           0.00001000           0.00000997           0.00000000


Это насчитала maxima
     [ - 2.992768282b-1   5.985538325b-6    4.001454189b-1   - 5.099975655b-7 ]
     [                                                                        ]
     [  4.029892624b-1   - 8.059760198b-6  - 1.994502193b-1  - 2.04038202b-6  ]
     [                                                                        ]
     [ - 1.482559332b-3   2.963966182b-8   - 2.788771941b-4   1.02014204b-6   ]
     [                                                                        ]
     [ - 3.01797179b-5    1.000060359b-5    9.967132764b-6   1.224243903b-10  ]

Можно заметить, что на float вариант по теореме немного отстает. Но это в данном конкретном случае.
А что со временем работы?
Одна и та же матрица миллион раз обращалась.
   for(size_t i=0;i<1000000;i++)
    {
        alpha=alpha.invert_transpose();
    }
Вариант с союзной матрицей: 390 308 мкс
          2.00000000           4.00000000           9.00000000           2.00000000 
          2.00000000           9.00000000           7.00000000      100000.00000000 
          4.00000000           3.00000000           8.00000000           0.00000100 
       4000.00000000        6633.00000000      995522.00000000           1.13000000 
390 308 микросекунд спустя ----------------------------
          2.00000000           4.00000000           9.00000000           2.00000000 
          2.00000000           9.00000000           7.00000000       99999.99999999 
          4.00000000           3.00000000           8.00000000           0.00000100 
       4000.00000000        6633.00000000      995521.99999991           1.13000000


Вариант по теореме: 1 291 526 мкс
           2.00000000           4.00000000           9.00000000           2.00000000 
          2.00000000           9.00000000           7.00000000      100000.00000000 
          4.00000000           3.00000000           8.00000000           0.00000100 
       4000.00000000        6633.00000000      995522.00000000           1.13000000 
1 291 526 микросекунд спустя----------------------
          1.99916579           3.99984523           8.99719176           1.99992263 
          2.00265273           9.00052080           7.00307486       99996.12998843 
          3.99982965           2.99988385           8.00050879           0.00000103 
       3999.84513206        6632.74319101      995483.45650841           1.13082955         


Замечу, что вариант по теореме здорово «нашумел» погрешностями.
На мой взгляд, накопление ошибки здесь берётся из делений и умножений при вычислении коэффициентов характеристического уравнения. В обычном способе деление идёт только в финале.
Тут может существенную погрешность вносить процесс возведения матрицы в степень. Если для этой матрицы не соблюден критерий устойчивости Ляпунова (действительная часть хотя бы одного собственного числа положительна), малые возмущения, которые сопровождают каждую арифметическую операцию, «раскачают» нашу линейную систему так, что в итоге мы получим матрицу, далекую от реальной матрицы A^n.

В методе же с союзной матрицей имеет место быть определенная симметрия. Очень возможно, что погрешность, выскочившая при получении одного минора в процессе вычисления вычитается из точно такой же погрешности, выскочившей при вычислении другого минора. Происходит своеобразная «стабилизация», компенсация погрешностей. Но это нужно скрупулезно доказывать.
Я небольшое отступление сделаю. Из применения теоремы Гамильтона-Кэли к обращению матриц получается интересная картина. Когда мы говорим о существовании обратной матрицы, предполагаем, что определитель матрицы A отличен от нуля (или все собственные числа этой матрицы должны быть ненулевыми). А тут получается, что для существования обратной матрицы нужно, чтобы коэффициент характеристического уравнения матрицы A^n был отличен от нуля.
Сильно подозреваю, что можно доказать: этот коэффициент на самом деле и есть определитель, возможно умноженный на константу.
Сейчас я прикинул, это и есть определитель, взятый с противоположным знаком (при записи и вычислениях я использовал перед ним знак "-"), поскольку является свободным членом в этом уравнении, а свободный член по теореме Виета равен произведению корней полинома, произведение характеристических чисел матрицы равно определителю. Ещё один способ его вычисления.
В C++14 в constexpr можно делать конструкторы и нормальные функции (в 11 только однострочные).
И это очень круто, если вместо const можно написать constexpr, тогда можно вовсе не парися о оптимизациях преде компиляцией, считая самому матрицы, и быть увереным что компилятор сам сложит выражения типа единичная матрица*перенос*поворот*инверс(перенос) в одну матрицу еще на компиляции.
Нарисованное перед функцией constexpr вовсе не гарантирует, что лентяй-компилятор вычислит ее значение во время компиляции. Гарантию дает только использование std::integral_constant:
cout<<std::integral_constant<int, g(0)>::value;
При чем тут вообще это? constexpr показывает что счисление возможно провести на этапе компиляции. Но это так же значит что функции помеченые constexpr можно вызывать и в коде не с constexpr выражения.
Библиотека линейной алгебры которая обязана все считать на компиляции мягко говоря, бесполезна ведь.
Да и вообще, меньше слов, больше кода — ideone.com/wRz8nr
Этот мини класс можно использовать и для генерации компайл-тайм констант и для любых рантайм данных.
Очень интересно, особенно строка 15:
return std::move ( type(lhs.x + rhs.x, lhs.y + rhs.y) );


Предполагать, что компилятор — законченный тупица, который не в состоянии делать NRVO — мягко говоря не совсем правильно.

Коротко: return std::move(x) — бессмысленная избыточность. Достаточно просто return x;

Это прямо описано в стандарте (12.8/32).
Обсуждение на stackoverflow
Снова не по теме. Это кусок кода который я написал скопировал из другого места, где std::move нужен был. Он демострирует НЕ std::move, который здесь вообще ничего не делает, и я это знаю.
Смысл первого моего коментария в том что было бы интересно сделать полноценную компайл-тайм адекватную библиотеку матриц для задачи туториала.
constexpr показывает что счисление возможно провести на этапе компиляции.

Так вам вроде об этом и сказали — только возможно, а не обязательно. Впрочем, я стандарт не читал, вдруг там гарантируется, что constexpr-функции от constexpr-выражений должны быть посчитаны на этапе компиляции? Это было бы здорово! Был бы рад, если кто-нибудь просветит по этому вопросу.
Не гарантирует этого стандарт, можно написать банальный тест и проверить. Только если результат пишется в constexpr — переменную, магия сработает. Собственно это и делает std::integral_constant()
Ну, тест это не гарантия, от багов никто не застрахован. Да и разработчики компиляторов любят вносить свои улучшения/отклонения от стандарта.

Только если результат пишется в constexpr — переменную, магия сработает.

Почему вот, кстати? Вроде должно быть очевидно, что это избыточное условие.
Гипотетически(!), можно предположить, что компилятор в таком случае резонно поинтересуется:
-А результат вычисления constexpr-функции я куда складировать буду? Вот переменную мне объявите, я в нее все сложу и в секцию статических данных выполняемого файла сохраню.

То есть здесь на лицо использование уже имеющегося в языке функционала для размещения статических данных.
если просто, то constexpr бывает в двух ипостасях:
— на переменных
— на функциях
Если в выражении используются только constexpr переменные, с constexpr фуркциями и литералы, то это выражение может быть вычислено в компайл тайме.
Любое такое выражение можно исползьвать там где нужна константа компиляции.
Так же функции constexpr можно использовать с динамическими данными, т.е. это никак не деградирует рантайм полезность библиотеки.
std::integral_constant это вообще тип, если что… Я окончательно перестал понимать что вы хотите сказать. Вам хочется что бы обязательно счисления проводились в компайл тайме? Ну тогда да, вы можете это зафорсить обвернув в этот тип, но все использованные функции Обязаны быть constexpr. Это не или/или.
Уф… Блин, походу тут какое-то недоразумение. constexpr показывает что функцию/объект можно использовать в качестве компайл тайм констант. Учитывая специфики современных компляторов — они сделают все что возможно в компайл тайме. То есть когда вы пишите int x = 5+4; вы ведь правда считаете что компилятор постчитает выражение 5+4 и вам не нужно прибавлять их самому? Когда делаете объекты и функции constexpr, это показывает компилятору так же что ваш объект можно так оптимизировать. Конечно, если полностью отключить оптимизацию он этого не сделает.

Да, еще плюс в том что функции помеченые constexpr так же работают и на динамических данных. Т.е. в рантайме те же функции будут так же работать.
GCC не лентяйничает с constexpr только в режиме жесткой оптимизации (-O3).
На -O2 он функцию только заинлайнил:
#include <iostream>
using namespace std;
constexpr unsigned int fact(unsigned int in)
{
  return  in==0
          ?
             1
          :
             in*fact(in-1);
}
int main() 
{
    cout << fact(8) <<'\n';
    return 0;
}

Компилируем:
g++ test.cpp -std=c++11 -O2 -S -o out.s

Смотрим листинг:
main:
.LFB3682:
	.cfi_startproc
	movl	$1, %esi
	movl	$8, %eax
	.p2align 4,,10
	.p2align 3
.L3:
	imull	%eax, %esi		;заинлайненное вычисление факториала.
	subl	$1, %eax		;GCC - умница, развернул рекурсию
	jne	.L3													;
	subq	$24, %rsp
	.cfi_def_cfa_offset 32
	movl	$_ZSt4cout, %edi
	call	_ZNSo9_M_insertImEERSoT_
	leaq	15(%rsp), %rsi

Компилируем с более жесткой оптимизацией:
g++ test.cpp -std=c++11 -O3 -S -o out.s

.cfi_startproc
	subq	$24, %rsp
	.cfi_def_cfa_offset 32
	movl	$40320, %esi              ;все честно, 40320 - это факториал 8
	movl	$_ZSt4cout, %edi
	call	_ZNSo9_M_insertImEERSoT_
	leaq	15(%rsp), %rsi

Посчитали факториал в maxima:
Maxima 5.35.1 http://maxima.sourceforge.net
using Lisp CLISP 2.49 (2010-07-07)
Distributed under the GNU Public License. See the file COPYING.
Dedicated to the memory of William Schelter.
The function bug_report() provides bug reporting information.
(%i1) 8!;
(%o1)                                40320
(%i2) 


Напоминаю, что по умолчанию в большинстве IDE стоит -O2 для релиза
А что будет если написать
constexpr auto a = fact(8);
cout << a <<'\n';
вместо
cout << fact(8); <<'\n';
?
В любом режиме оптимизации считает в compile-time, как и положено по стандарту.

Итого: Чтобы применить вашу идею, и чтобы это было переносимо между разными компиляторами и их режимами, придется все такие места внимательно оборачивать в std::integral_constant.

Иначе получится, например, что при переключении IDE в Debug (-O0) будет жуткая посадка производительности.
Общим, похоже у вас единственная цель — доказать свою правоту, при чем не понятно в каком вопросе. Я не собираюсьразводить споров ради споров. У меня все работает при -O1. Дебаг — он для дебага. Он вообще не разворачивает ничего. Попробуйте в дебаге обвернуть что-то в std::integral_constant и сильно удивитесь.
У меня цель — сделать корректно, надежно и переносимо.

Вы утверждаете что можно везде якобы нарисовать constexpr, после чего компилятор якобы все возьмет и разом вычислит.

Я замечаю, что constexpr вовсе не обязывает компилятор что-то там считать, ему нужно явно указать, что результат пишется в constexpr.

Кстати, в режиме отладки,
g++ test.cpp -std=c++11 -O0 -g -S -o out.s

выражение обернутое в std::integral_constant
cout << std::integral_constant<unsigned int,fact(8)>::value;

Прекрасно сосчиталось во время компиляции:
main:
.LFB3639:
	.file 1 "test.cpp"
	.loc 1 14 0
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	.loc 1 16 0
	movl	$40320, %esi                ;ага, вот эта константа
	movl	$_ZSt4cout, %edi
	call	_ZNSolsEj
	movl	$10, %esi
	movq	%rax, %rdi
1) А как выглядит дебаг для такого кода? Мы ведь правда хотим дебажить наши функции и только для этого делаем билды с -O0.

2) Кстате, заметте, что что бы использовать fact в integral_constant вам необходимо сделать его constexpr. Именно об этом я и говорил. Уже 5 раз. Что если сделать функцию constexpr — вы ее можете использовать как копайл константу, например в типе integral_constant. Но при этом это не ограничивает ее использвание. Мы так же может использовать fact от пользовательских данных. Пользовательские данные никак не обернуть в integral_constant, потому что это противоречит смыслу integral_constant.
Я вообще не понимаю что вы хотите сказать? Что обязательно все обворачивать в integral_constant? Спасибо, за меня это сделает даже О1 оптимизация.
Вы правильно заметили — отлаживать код, который выполняется в compile-time невозможно. Также, как невозможно отлаживать, например, раскрутку рекурсии на шаблонах.

Я хочу сказать и показать, что для того, чтобы значение constexpr-функции гарантированно вычислилось в compile-time, его необходимо сохранять в статическую константу. Если этого не сделать, вычисление на этапе компиляции будет выполняться только в режиме агрессивной оптимизации -O3, а не -O1, как вы утверждаете:
Спасибо, за меня это сделает даже О1 оптимизация
Я это всегда знал ведь ;) Мы просто непоняли друг друга сначала.
Думаю мы поняли друг друга. Общим, все что я хочу это что бы вот что было:
constexpr int det = matrix([1,4,9],[2,9,7],[4,3,8]).det();
cout << std::integral_constant<int, det >::value;
ну или как-то так.
После строки:
constexpr int det=...

оборачивание det в std::integral_constant бессмысленно — запись в constexpr-переменную гарантирует вычисление в compile-time.

Я собираюсь сделать весь рендер во время компиляции, не только матричную алгебру.
Вобще смысл писать «constexpr int det» в том что можно просто удалить слово constexpr и тогда можно дебаггером зайти внутрь выражения. integral_constant действительно лишний семантически, он для выразительности что это точно просто константа в бинарнике.
Вообще очень клевая идея сделать рендерер в компиляторе. А как обойдетесь io?
Входные данные поместить в строку, выходные — либо в кортеж, либо в массив.
В любом режиме оптимизации считает в compile-time, как и положено по стандарту.
Честно говоря, я не уверен, что стандарт где-то обязывает так делать. В конце концов, у нас есть правило «as-if», а поскольку программы с вычислением выражения в compile-time и в runtime имеют одно и то же наблюдаемое поведение, то они обе корректны. На практике, конечно, понятно, что когда мы используем выражение как параметр шаблона, то компилятор его посчитает во время компиляции, просто чтобы проверить, что не будет ошибки компиляции, дать имя этому шаблону и т.д. Тут было лучше какую-нибудь опцию компилятора иметь, которая говорила бы ему «считай все constexpr в compile-time», но для gcc в документации я такой не нашел.
Здесь можно за уши притянуть тот факт, что значение в constexpr должно быть доступно во время компиляции.

Но формально, если не заставлять компилятор силком (используя std::integral_constant, для которого это значение и будет параметром шаблона), компилятор может и слентяйничать.

Итого: с «положено по стандарту» я действительно погорячился.
Мне кажется, с переменными та же самая история, что и с параметром шаблона. Во-первых, компилятор должен проверить, что значение, которым она инициализируется, вычислимо в compile-time, и если нет, то выдать ошибку компиляции. Причем для этой проверки недостаточно просто проверить, что функция и её аргумент constexpr, например:
int non_constexpr() { return 1; }

constexpr int f(bool b)
{
    return b ? 0 : non_constexpr();
}

constexpr int x = f(true); // OK
constexpr int y = f(false); // Compilation error

Во-вторых, переменная может быть использовано в контексте, требующем знания её значения в момент компиляции (параметр шаблона, размерность массива, static_assert()), поэтому, возможно, компилятору тупо проще вычислить значение сразу, чем проводить анализ, а будет ли оно нам нужно, или же сохранять какие-то данные для ленивого вычисления.
Общим, у меня все работает на -O1 — goo.gl/XVhbOJ
Я ж не говорю что оно работает просто так. Constexpr тоже готовить нужно уметь. Вот для сравнения ваш код goo.gl/bR14sW
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации