template <typename T>
constexpr int my_log2(T n)
{
return n? 1+my_log2(n>>1): -1;
}
int main()
{
return my_log2<unsigned>(42);
}
И VC , и G++ генерят обычные вызовы даже в релизе.
Вычисления в compile time производятся только если компилятор припереть к стенке,
напр. передать параметром в шаблоне:
template <int I> struct eval_im
{
enum { value = I };
};
Это нормально?
Это как гордая птица ёж, которая без пинка не полетит.
Еще вопрос. Поскольку на constexpr функции накладываются ограничения, ее реализация бывает неэффективна.
Можно ли сделать две версии функции constexpr и не constexpr, но с одинаковым именем и списком параметров?
Здравствуйте, Дрободан Фрилич, Вы писали:
ДФ>Еще вопрос. Поскольку на constexpr функции накладываются ограничения, ее реализация бывает неэффективна. ДФ>Можно ли сделать две версии функции constexpr и не constexpr, но с одинаковым именем и списком параметров?
Здравствуйте, niXman, Вы писали:
X>все проверенные мною компиляторы(gcc-6.3-linux, mingw-w64-7.1-windows, clang-4.0-linux, intel-17.0-linux) генерят одинаковый код, приведенный мною ранее.
Это хорошо, конечно. Но необязательно и зависит от ключей:
clang -O1: run time
clang -O2: compile time
gcc -O0: run time
gcc -O1: compile time
gcc -O2: compile time
The correct answer — as stated by Herb — is that according to the standard a constexpr function may be evaluated at compiler time or run time unless it is used as a constant expression, in which case it must be evaluated at compile-time. To guarantee compile-time evaluation, we must either use it where a constant expression is required (e.g., as an array bound or as a case label) or use it to initialize a constexpr. I would hope that no self-respecting compiler would miss the optimization opportunity to do what I originally said: "A constexpr function is evaluated at compile time if all its arguments are constant expressions."
Здравствуйте, Conr, Вы писали:
C>Это хорошо, конечно. Но необязательно и зависит от ключей:
т.к. в топике шла речь про релиз — я тестил релиз.
C>"A constexpr function is evaluated at compile time if all its arguments are constant expressions."
наш случай.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Дрободан Фрилич:
ДФ>Еще вопрос. Поскольку на constexpr функции накладываются ограничения, ее реализация бывает неэффективна. ДФ>Можно ли сделать две версии функции constexpr и не constexpr, но с одинаковым именем и списком параметров?
Пока что сделать две реализации одной функции для constexpr и не-constexpr контекстов вызова нельзя. Есть предложение ввести оператор constexpr: p0595r0.
Здравствуйте, N. I., Вы писали:
NI>Фраза выдрана из контекста, это была всего лишь хотелка.
эта хотелка уже работает во всех пртестированных мной компиляторах.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
N. I.:
NI>Дрободан Фрилич: NI>Пока что сделать две реализации одной функции для constexpr и не-constexpr контекстов вызова нельзя. Есть предложение ввести оператор constexpr: p0595r0.
У тебя странная фишка, вырезать при цитате "здравствуйте," и "вы писали".
Если определение функции доступно в каждой единице трансляции, где её вызывают (что типично для шаблонных и inline функций), то ничто не мешает компилятору попробовать вычислить её на этапе компиляции независимо от того, есть у неё спецификатор constexpr или нет. Наличие у функции спецификатора constexpr может лишь дать оптимизатору подсказку, что вероятность существования возможности посчитать её в compile time выше, чем в случае с "обычными" функциями, при этом в общем случае constexpr не даёт гарантий, что функцию можно посчитать в compile time при вызове её с любыми аргументами, значения которых известны на этапе компиляции. Например, здесь
template <typename T>
constexpr int my_log2(T n)
{
if (n <= 0)
std::terminate();
if (n == 1)
return 0;
return 1 + my_log2(n >> 1);
}
int main()
{
constexpr int x = my_log2(42);
constexpr int y = my_log2(0);
std::cout << x << std::endl;
}
существует возможность вычислить my_log2(42) в compile time, но для my_log2(0) такой возможности нет — подобный вызов можно посчитать только в run time, несмотря на то, что в функцию передали вполне себе константный ноль.
niXman:
NI>>Вот только не везде прослеживается связь оптимизации с объявлением функции как constexpr.
X>ну да, глупо использовать в constexpr-функции сисколы и жаловаться, что она не constexpr.
В том примере std::terminate ни на что принципиально не повлияло (присутствие такого вызова не делает функцию non-constexpr), GCC и Clang одинаково соптимизировали весь код до
mov eax, 5
ret
как при наличии, так и при отсутствии спецификатора constexpr у функции.
The correct answer — as stated by Herb — is that according to the standard a constexpr function may be evaluated at compiler time or run time unless it is used as a constant expression, in which case it must be evaluated at compile-time. To guarantee compile-time evaluation, we must either use it where a constant expression is required (e.g., as an array bound or as a case label) or use it to initialize a constexpr. I would hope that no self-respecting compiler would miss the optimization opportunity to do what I originally said: "A constexpr function is evaluated at compile time if all its arguments are constant expressions."
Кстати, если рассматривать строго формальные гарантии, то даже в таком варианте
#include <exception>
template <typename T>
constexpr int my_log2(T n)
{
if (n <= 0)
std::terminate();
if (n == 1)
return 0;
return 1 + my_log2(n >> 1);
}
int main()
{
constexpr int x = my_log2(42);
using t = char[my_log2(43)];
return x + sizeof(t);
}
вычисление my_log2(42) и my_log2(43) только на этапе трансляции стандартом не гарантировано. Observable behavior может быть одинаковым в любом случае — что при вычислении в compile time, что при вычислении в run time, как если бы программа была заменена на следующую:
#include <exception>
template <typename T>
int my_log2(T n)
{
if (n <= 0)
std::terminate();
if (n == 1)
return 0;
return 1 + my_log2(n >> 1);
}
int main()
{
int x = my_log2(42);
auto sizeof_t = my_log2(43);
return x + sizeof_t;
}
Только в случае с
constexpr int x = my_log2(42);
using t = char[my_log2(43)];
return x + sizeof(t);
компилятору всё же придётся как-то выяснять, что вызовы my_log2(42) и my_log2(43) удовлетворяют требованиям константных выражений, а значение my_log2(43) ещё и допустимо использовать в качестве размера массива.
Правило "as if" теоретически работает в обе стороны: компилятор волен не только "оптимизировать", но и "пессимизировать". В данном случае my_log2(42) и my_log2(43) могут быть (в теории) вычислены и в compile time (для определения валидности программы) и в run time (для определения значения, которое надо вернуть из main). Конечно, на практике вероятность встретить реализацию, которая делала бы такую "оптимизацию наоброт", почти нулевая.
N. I.:
NI>Если определение функции доступно в каждой единице трансляции, где её вызывают (что типично для шаблонных и inline функций), то ничто не мешает компилятору попробовать вычислить её на этапе компиляции независимо от того, есть у неё спецификатор constexpr или нет. Наличие у функции спецификатора constexpr может лишь дать оптимизатору подсказку, что вероятность существования возможности посчитать её в compile time выше, чем в случае с "обычными" функциями, при этом в общем случае constexpr не даёт гарантий, что функцию можно посчитать в compile time при вызове её с любыми аргументами, значения которых известны на этапе компиляции.
Тут может быть скрываться проблема совместимости. Компилятор A даёт послабления и съест исходник, а компилятор B выплюнет. Или даже две версии одного компилятора.
Программист, понадеявшийся на нестандартную фичу создаст крайне непортабельный код, который фиг откомпилируешь.
Конкретный пример, один компилятор проверяет все ветки на соответствие constexpr-у, а другой только те что вычисляются.
На хабре был пример https://habrahabr.ru/post/228181/ , бросать исключение в дохлых ветках констекспра формальное не запрещалось. Такой финт ушами использовался для совместимости compile-time и run-time по ошибкам.
Оказалось не все компиляторв такое могут.