Здравствуйте, LaptevVV, Вы писали:
LVV>Ну так, может быть, просто разные компилеры используются?
Конечно разные. Но смысл не в том, что они разные, а в том, что результат шаблона из первого сообщения неопределён. И поэтому такой код не нужно писать или использовать. То что разные компиляторы выдают разные ответы — лишь следствие проблемы в исходном тексте.
Ну и если уж разговор зашёл об компиляторах, то, например, gcc-5.2 компилирует исходный пример, а clang-3.7 выдаёт ошибку. С другой стороны, если пример чуть-чуть переписать пользуясь средствами только C++03, то ситуация может изменится на противоположную: gcc-5.2 теперь находит бесконечную рекурсию, а clang-3.7 выдаёт ответ. Так что тут даже нельзя утверждать что gcc или clang хуже другого справляются с таким кодом — оба иногда сообщают об ошибке (что хорошо), а иногда выдают численный ответ (что на самом деле хуже, так как может замаскировать ошибки в более сложных случаях).
Ну и если уж выбирать способ считать биты, то куда лучше вообще вместо шаблонов взять constexpr-функции — в них правила рекурсии другие, более интуитивно понятные и с ними всё работает во всех компиляторах, удовлетворяющих стандарту:
Здравствуйте, ArtDenis, Вы писали:
AD>В дополнении к другим придиркам тоже добавлю что Val=UInt(-1) лучше заменить Val=UInt(~0). Так "более" правильно, т.к. представление отрицательных чисел может быть разным (в случае представления под названием "код со сдвигом" твой код будет выдавать некорректный результат).
В любом случае это не будет работать для знаковых типов потому как "Val>>1" немного не определен
Если хотим поддерживать и знаковые числа нужно вызывать std::make_unsigned .
Здравствуйте, ArtDenis, Вы писали:
Ш>>Считаем число бит в беззнаковом типе.
AD>В дополнении к другим придиркам тоже добавлю что Val=UInt(-1) лучше заменить Val=UInt(~0). Так "более" правильно, т.к. представление отрицательных чисел может быть разным (в случае представления под названием "код со сдвигом" твой код будет выдавать некорректный результат).
А чем Val=UInt(~0) лучше Val=UInt(-1)?
UInt(-1) гарантированно даст наибольшее целое влезающие в беззнаковый UInt. А разве можно сказать то же про UInt(~0)?
error: recursive template instantiation exceeded maximum depth
А всё из-за того, что из Bits<T, 0> идёт бесконечно рекурсивный вызов к Bits<T, 0>. Если бы это были обычные функции, то такой рекурсивный вызов не происходил бы из-за условия в тернарном операторе, но для шаблонов соответствующую ветку компилятор всё равно обязан рассмотреть.
Ш>Теперь некоторые вещи можно упростить.
Вот сначала приведи в соответствие со стандартом предыдущий код, а потом сравни простоту и понятность с тем же
std::numeric_limits<UInt>::digits
Ну или с вариантом, завёрнутым в свой шаблон с аналогичным твоему именем:
template <class UInt> constexpr auto BitsOf = std::numeric_limits<UInt>::digits;
Здравствуйте, watchmaker, Вы писали:
W>Здравствуйте, Шахтер, Вы писали:
Ш>>Считаем число бит в беззнаковом типе.
W>Не считает:
error: recursive template instantiation exceeded maximum depth
А всё из-за того, что из Bits<T, 0> идёт бесконечно рекурсивный вызов к Bits<T, 0>. Если бы это были обычные функции, то такой рекурсивный вызов не происходил бы из-за условия в тернарном операторе, но для шаблонов соответствующую ветку компилятор всё равно обязан рассмотреть.
У меня считает.
Ш>>Теперь некоторые вещи можно упростить. W>Вот сначала приведи в соответствие со стандартом предыдущий код, а потом сравни простоту и понятность с тем же
template <class UInt> constexpr auto BitsOf = std::numeric_limits<UInt>::digits;
error: recursive template instantiation exceeded maximum depth
А всё из-за того, что из Bits<T, 0> идёт бесконечно рекурсивный вызов к Bits<T, 0>. Если бы это были обычные функции, то такой рекурсивный вызов не происходил бы из-за условия в тернарном операторе, но для шаблонов соответствующую ветку компилятор всё равно обязан рассмотреть.
Ну так, может быть, просто разные компилеры используются?
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
ну а теперь представь себе 80-битовое число (например, с плавающей точкой), которое архитектура требует выравнивать по 16-байтовым границам.
sizeof такого числа будет 16, а не 10.
Здравствуйте, watchmaker, Вы писали:
W>Здравствуйте, LaptevVV, Вы писали:
LVV>>Ну так, может быть, просто разные компилеры используются?
W>Конечно разные. Но смысл не в том, что они разные, а в том, что результат шаблона из первого сообщения неопределён.
A>есть же constexpr функции, зачем писать такую нечитаемую штуку
Ну не знаю, я вполне себе читаю.
А constexpr-функции использовать тоже можно, но тогда придётся делать две декларации -- сначала функцию, а потом константу.
Здравствуйте, Шахтер, Вы писали:
Ш>Считаем число бит в беззнаковом типе.
В дополнении к другим придиркам тоже добавлю что Val=UInt(-1) лучше заменить Val=UInt(~0). Так "более" правильно, т.к. представление отрицательных чисел может быть разным (в случае представления под названием "код со сдвигом" твой код будет выдавать некорректный результат).
Здравствуйте, ArtDenis, Вы писали:
AD>Здравствуйте, Шахтер, Вы писали:
Ш>>Считаем число бит в беззнаковом типе.
AD>В дополнении к другим придиркам тоже добавлю что Val=UInt(-1) лучше заменить Val=UInt(~0). Так "более" правильно, т.к. представление отрицательных чисел может быть разным (в случае представления под названием "код со сдвигом" твой код будет выдавать некорректный результат).
Эта конструкция только для беззнаковых чисел. Да и речь не о том. Подсчет числа битов взят только как пример. Речь о рекурсии.
Здравствуйте, B0FEE664, Вы писали:
BFE>UInt(-1) гарантированно даст наибольшее целое влезающие в беззнаковый UInt.
С какой это стати?
BFE>А разве можно сказать то же про UInt(~0)?
Сложно сказать. Надо посмотреть что прописано в стандарте насчёт каста (int)~0 к типу unsigned long long. Возможно что и ~0 тоже не катит для данного случая.
Здравствуйте, ArtDenis, Вы писали:
BFE>>UInt(-1) гарантированно даст наибольшее целое влезающие в беззнаковый UInt. AD>С какой это стати?
Следствие из 4.7/2 стандарта.
BFE>>А разве можно сказать то же про UInt(~0)? AD>Сложно сказать.
Вот именно.
AD>Надо посмотреть что прописано в стандарте насчёт каста (int)~0 к типу unsigned long long. Возможно что и ~0 тоже не катит для данного случая.
Прежде чем к касту переходить, надо выяснить что даст ~0.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Шахтер, Вы писали:
Ш>>Теперь некоторые вещи можно упростить.
К>Грабельки. Не надо такое тащить в продакшен.
К>http://ideone.com/a83SVo К>
К>#include <iostream>
К>using namespace std;
К>template<int X> int const num = X==1 ? 0 : 1 + num<X%2 ? X*3+1 : X/2>;
К>//template<int X> int const ccc = X==0 ? 0 : 1 + ccc<0>; // ошибка: константа ccc<0> явно определена через саму себя
К>template<int X> int const rec = X==0 ? 0 : 1 + rec<(X ? 0 : 0)>; // поэтому прибегнем к трюку, сделаем вид, что параметр вычисляется
К>template<int X> int const err = X==0 ? 0 : 1 + err<(X ? X : X)>;
К>template<int X> int constexpr eee = X==0 ? 0 : 1 + eee<(X ? X : X)>;
К>template<int X> int const inf = X==0 ? 0 : 1+inf<X-1>;
К>int n[num<27>];
К>int r[rec<10>];
К>//int e[err<10>]; // это не константа времени компиляции, а статическая переменная
К>//int e[eee<10>]; // зацикливание в constexpr
К>int main() {
К> cout << num<27> << endl; // 111 - честно вычислил рекурсию
К> cout << rec<10> << endl; // 1 - честно вычислил рекурсию
К> cout << err<10> << endl; // 1 - особенности инициализации статической переменной
К>//cout << eee<10> << endl; // ошибка: зацикливание в constexpr
К>//cout << inf<10> << endl; // ошибка: исчерпание глубины рекурсии, inf<-899>
К>}
К>
Скобки забыл поставить в тернарном операторе. Это так и было задумано?
В целом я так и не понял, чего ты добивался.
Здравствуйте, Шахтер, Вы писали:
Ш>Скобки забыл поставить в тернарном операторе. Это так и было задумано?
Скобки не при чём.
Ш>В целом я так и не понял, чего ты добивался.
Показать, что код слишком стрёмный.
Некоторые константы являются constexpr'ами, другие — статическими переменными константного типа.
Некоторая рекурсия возможна только с трюками.
Бесконечная рекурсия — в некоторых случаях останавливается с трюками, в других не останавливается, хотя должна, в третьих останавливается, хотя не должна.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Шахтер, Вы писали:
Ш>>Скобки забыл поставить в тернарном операторе. Это так и было задумано?
К>Скобки не при чём.
Вообще-то причем. Тернарный оператор ленивый.
Ш>>В целом я так и не понял, чего ты добивался.
К>Показать, что код слишком стрёмный.
Нууу. Пройдет пару лет, отполируют стандарт, починят компиляторы...
А пока я экспериментирую.
Вот тебе ещё на закуску.
Здравствуйте, Шахтер, Вы писали:
Ш>>>Скобки забыл поставить в тернарном операторе. Это так и было задумано? К>>Скобки не при чём. Ш>Вообще-то причем. Тернарный оператор ленивый.
У тернарного оператора самый низкий приоритет, скобки там подразумеваются.
Хоть он и ленивый, но компилятор всё равно разворачивает шаблон до посинения, — прежде чем приступить собственно к вычислению.
Поскольку посинение там не наступает, то компилятор доходит до предельной глубины рекурсии и сдаётся.
Почему это может потребоваться. Дело в том, что тип переменной может зависеть от параметра
// метафункция: тип по номеруtemplate<class T> struct deftype { using type = T; };
template<int N> struct nth : deftype<unsigned char> {};
template<> struct nth<1> : deftype<unsigned long long> {};
template<> struct nth<2> : deftype<unsigned int> {};
template<> struct nth<3> : deftype<unsigned short> {};
template<int N> using result = typename nth<N>::type;
// константаtemplate<int N> const auto value = result<N>(0);
template<int N> const auto branch = true ? foo<N>() : bar<N>();
// где выражения foo<N>() и bar<N>() каких то разных, но совместимых типов
Я не смог с разбегу придумать показательный пример, чтобы именно приведение к общему типу дало эффект. Не считая sizeof, конечно.
Но в процессе экспериментов получил ICE у компилятора на ideone.com
template<int N> const auto ice = true ? 0 : ice<(N>0 ? N-1 : 0)>;
const auto crash = ice<1>;
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Шахтер, Вы писали:
Ш>>Не вижу причин, почему на шаблонах должно быть иначе.
К>Значения вычисляются лениво, а типы аргументов тернарного оператора — энергично.
Не вижу никаких проблем с вычислением типов. Тип константы известен --он задекларирован.
Здравствуйте, Шахтер, Вы писали:
Ш>Не вижу никаких проблем с вычислением типов. Тип константы известен --он задекларирован.
То, что очевидно тебе, не очевидно компилятору.
Заведомо известные типы операндов — это частный случай.
Так же, как заведомо известное значение условия.
Ну и ещё одно ICE на эту тему — http://ideone.com/yxsgWl
То есть, механизм прикольный, но граблистый.
Здравствуйте, uzhas, Вы писали:
К>>У тернарного оператора самый низкий приоритет, скобки там подразумеваются. U>формально, здесь речь не о приоритетах, а об особенности грамматики
Я слово "приоритет" употребил в этом смысле, в грамматическом. То, что в англоязычной cs-школе называется precedence.
Да если бы ветвление стало конфликтовать с подстановкой параметров шаблона или с прочей арифметикой, код просто не скомпилировался бы: конкретно в этом месте двузначность невозможна.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Шахтер, Вы писали:
Ш>>Не вижу никаких проблем с вычислением типов. Тип константы известен --он задекларирован.
К>То, что очевидно тебе, не очевидно компилятору. К>Заведомо известные типы операндов — это частный случай. К>Так же, как заведомо известное значение условия.
К>Ну и ещё одно ICE на эту тему — http://ideone.com/yxsgWl К>То есть, механизм прикольный, но граблистый.
Всё новое всегда граблистое. Ничего страшного, пройдет несколько лет, отполируют стандарт, доведут компиляторы и всё будет ОК.