Комментарии 95
Интересное решение, хотя при использовании C extensions (gcc), можно обойтись без функторов вообще — за счет Label Values. Эта функциональность применяется в низкоуровневых модулях высоконагруженных приложений для организации переходов без ветвления, хотя в зависимости от ситуации может быть оптимальнее использовать if в сочетании с builtin_expect для мануальной настройки branch prediction. Вместо отрицания, возможно, применить более подходящий оптимальный intrinsic
int main() {
int n = 100500;
void* refs [] = { &&work, &&done };
work:
printf("%d ", n);
n--;
goto *refs[!n];
done:
return 0;
}
while( !!n ) printf( "%d ", n-- );
или, что то же самое, while( n ) printf( "%d ", n-- );
Более того, что подразумевается под «реализация функции вывода на печать не в счет»?
Предлагается ещё реализовать функцию отрисовки что ли? Если нет, то тогда в чём вообще проблема? В функции, осуществляющей печать, можно делать что угодно.
А лямбды на сишке можно полноценно эмулировать без условий? Тогда можно просто задачу в кодировке Черча переписать.
Вы же понимаете что оно неявно преобразуется к виду `!!n == true. Аналогично if и switch, хотя для switch clang умеет генерировать таблицу сдвигов
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
union Index
{ int32_t n;
struct
{int32_t : 31,
sign : 1;
};
};
void dummy(int){}
typedef void (*F)(int);
F check[2] = { dummy, exit };
int main()
{ Index index;
for( index.n = 100500;;index.n-- )
{ check[ index.sign ](0);
printf( "%d ", index.n );
}
return 0;
}
Вместо exit можно использовать longjmp. Хотя, конечно, — это платформо-зависимый код.Если бы можно было использовать for, while и прочее, то задачи бы не было.
А почему нельзя for? Ведь в виде for(;;) это всё равно, что goto label: скрытого сравнения нет, явного тоже нет.
int main()
{
int n = 9;
while (printf("%.d", n--));
printf("0");
return 0;
}
Однако тут используется цикл с условием. Если брать в широком смысле, то так или иначе задача не решаема без цикла или рекурсии, но рекурсия ограничена. Если исключить любое сравнение то подходит только бесконечный цикл.
Прервать его можно следующими способами:
1. break
2. return из main фунции
3. Бросить исключение
4. goto
5. Функция exit() или ее аналоги
Первые четыре варианта требуют какого то условия что бы не выйти из цикла раньше чем нужно, а это неизбежное сравнение. Остается только четвертый вариант, в комментариях уже оставляли подобный пример, вот мой:
#include <cstdio>
#include <stdlib.h>
void print(int n) { printf("%d\n", n); }
void printAndExit(int n) { print(n); exit(0); };
void(*f[])(int) = { printAndExit, print };
int main()
{
int n = 9;
for (;;)
{
f[bool(n)](n);
--n;
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
void not0(int i) {printf("%d\n", i);}
void is0(int i) {printf("0\n"); exit(0); }
void (*func[2])(int)={is0,not0};
void main()
{
for (int i=100;;i--) {
unsigned int j=((unsigned short*)&i)[0] | ((unsigned short*)&i)[1];
j=((unsigned char*)&j)[0] | ((unsigned char*)&j)[1];
j=(j&1)|((j&2)>>1)|((j&4)>>2)|((j&8)>>3)|((j&0x10)>>4)|((j&0x20)>>5)|((j&0x40)>>6)|((j&0x80)>>7);
func[j](i);
}
}
int isNonNull (float n) { return (int)(n / (n + 0.1) + 0.1); }
int isNegative(int n) { return (int)(n - sqrt(n*n)); }
int main()
{
int n = 3;
while(isNonNull(n) && !isNegative(n))
printf("%d", n--);
}
Куда слать резюме?
print(n--);
n+1 раз.Просматривая протоколы собеседований на позицию разработчика, обнаружил такую задачу
Это задачка с искусственными ограничениями (в том плане, что вряд ли они встретятся в реальной задаче компании), поэтому давать её на собеседовании, конечно, можно, но ИМХО только в случае собеседования в клуб извращенцев.
PS для тех кто не понял, преобразования к отрицательному будет давать в старшем бите 1 в любом случае кроме того когда изначальное значение 0. Следовательно, сдвинут на (8*sizeof(n)-1) мы получим либо 0 в случае когда изначально был 0 либо 1 в случае любого другого числа. Дальше просто при вызове «нулевой» функции делаем exit.
* без выделения памяти и конструирования объектов
* без exit(), чтобы не нарушать красивый ход исполнения кода
* без функторов и ограничения глубины рекурсии
* без явных и неявных сравнений включая, но не ограничиваясь: приведения к bool, operator!(), и всякие cmovz / jcxz в ассемблерном коде.
#include <cstdio>
#include <cstdint>
#include <windows.h>
int isNegative(int n)
{
return (unsigned int)n >> (sizeof(n) * 8 - 1);
}
void f(int number)
{
uint16_t hope = 0;
m1:
int isNeg = isNegative(number);
uint32_t fmt = 0x000a6400 | ((1 - isNeg) * '%');
printf((char*)&fmt, number--);
uint16_t magic = hope ^ (0x6F6F * isNeg + 0x0100);
__asm
{
mov ecx, m2;
mov ax, [magic];
or [hope], ax;
xor [ecx], ax;
mov eax, m1;
m2: jmp ecx;
}
}
int main()
{
DWORD old;
VirtualProtect(GetModuleHandle(nullptr), 0x16000, PAGE_EXECUTE_READWRITE, &old); // this might work
VirtualProtect(GetModuleHandle(nullptr), 0x2000, PAGE_EXECUTE_READWRITE, &old); // ... this one will fix when previous fails
int n = 30;
f(n);
}
Тестировал на msvc 2017 Win7, debug и release сборки, на n = -3, 0, 3, 1000, ...;
Вдохновлялся кодом masterspline
Не работает для отрицательных N.
мой вариант: https://godbolt.org/z/aPF4HE
заодно можно посмотреть на наличие неявных сравнений уже в сгенерированном коде.
1) Пишем свой обработчик на ошибку division by zero, оный должен увеличить сохраненный адрес возврата на длину команд присваивания, деления и безусловного перехода(надо посмотреть в листинге)
2) Закидываем его в таблицу прерываний
3) Далее адов говнокод:
void destroy (int n)
{
int i=99;
int j;
n++;
hitbyvelociraptor:
n--;
printf("%x\n",n);
j=i/n;
goto hitbyvelociraptor;
printf("done\n");
}
Задачей обработчика деления на ноль будет перепрыгнуть через goto. И никаких сравнений тут вообще нигде не будет делаться.
begin:
print(N);
N=N/N*(N-1);
goto begin;
Программа напечатает числа от N до 0, а потом прекратит работу.
struct Item
{
Item() { printf ("%d ", N - (this - items)) - 1; }
static void Do() { exit(0)}
static const size_t N = 100;
static const Item items[N+ 1];
};
const Item Item::items[] = {};
void main()
{
Item::Do();
}
#include <stdio.h>
template <int N> void print(int base)
{
print<N/2>(base);
print<N-N/2>(base-N/2);
}
template <> void print<0>(int)
{}
template <> void print<1>(int base)
{
printf("%d\n", base);
}
#define N 100
int main()
{
print<N+1>(N);
return 0;
}
#include <iostream>
struct printnum
{
printnum()
{
std::cout << nump-- << "\n";
}
static unsigned nump;
};
unsigned printnum::nump;
int main()
{
printnum::nump = 10000000;
printnum * pa = new printnum[printnum::nump+1];
}
динамическое выделения памяти, чтобы обойти ограничение по стеку
static не разрешит задавать размер динамичски
#include <iostream>
struct printnum
{
printnum()
{
std::cout << nump-- << "\n";
}
static unsigned long nump;
};
unsigned long printnum::nump;
char* memory[10];
int main()
{
printnum::nump = 10000000000000;
printnum * pa = new(memory) printnum[printnum::nump+1];
}
А то я тоже могу просто написать std::transform(nullptr, nullptr + n, (ничего не делающий output it), (функция вывода))
(но за хак я плюс поставил)
Удалено.
#include <iostream>
#include <vector>
#include <functional>
struct Caller
{
std::function<void()> & m_f;
Caller( std::function<void()> & f) : m_f(f) {}
~Caller() { m_f(); }
};
int main()
{
int n = 500;
std::function<void()> counter ([&n] {
std::cout << n-- << std::endl;
});
std::vector<Caller> v(n, Caller(counter));
return 0;
}
#include <iostream>
class A {
public:
A() {
std::cout << n << "\n";
--n;
}
static void set(size_t i) {
n = i;
}
private:
static size_t n;
};
size_t A::n;
void print_n_0(size_t n) {
A::set(n);
delete[] new A[n+1];
}
int main() {
print_n_0(10);
return 0;
}
#include <stdio.h>
int step(int i) {
printf("%d\n", i--);
return (i && step(i)) || 0;
}
int main(void) {
printf("%d\n", step(5));
return 0;
}
int main()
{
const int n = 10;
int count = n;
std::generate_n(std::ostream_iterator(std::cout, " "), n, [&count]() { return count--; });
return 0;
}
int prt(int n)
{
std::cout << n << "\t";
return n && prt(n - 1);
}
Можно, конечно, сказать, что && — скрытый оператор сравнения. Ну так, например, изначальный вариант автора поста на шаблонах — это, фактически, составное определение функции Haskell-style (отдельно для нуля, отдельно для всего остального, не помню уже, как именно оно называется в функциональных языках, нувыпонели), там тоже по-любому скрытый оператор сравнения (сравнить аргумент с нулём, по результатам выполнить либо то, либо это).
void print_a(int len){
cout << "\n===" << len << "\n";
float tmp = 5 / (len);
print_a(len - 1);
}
void handler(int a) {
exit (0);
}
int main ( ) {
int len = 100000;
signal(SIGFPE, handler);
print_a(len);
return 0;
}
Добавлено чуть позже — В смысле меньше локальных переменных вызвать. Но не факт что вообще лучше — все же по ссылке и с len-- мы будем менять переменную извне, что может быть не гуд. Можно и еще компактнее и надежнее(в память не упремся из-за рекурсии):
void handler(int a) {
exit (0);
}
int main ( ) {
int len = 100000;
signal(SIGFPE, handler);
l1:
cout << "\n===" << len-- << "\n";
float tmp = 5 / len;
goto l1;
}
Цикл for с тем же !!n, и нет сравнения ведь?
for (int n = N; !!n; --n)
printf("%d\n", n);
printf("%d\n", 0);
Можно было просто написать рекурсивную функцию
void out(int n) {
if (!n)
return;
printf("%d\n", n);
out(n-1);
}
printf("%d\n", 0);
Хвостовая рекурсия оптимизируется, и не будет никакой рекурсии в ассемблере.
!n это то же самое, что n==0;
А еще, среди целых чисел бывают такие, как -1, -2…
int main()
{
int n = 10;
try
{
while(1)
{
printf("%d\n", n);
n -= n / n;
}
}
catch(...){}
return 0;
}
#include <stdio.h>
#include "p99/p99.h"
#define STARTVAL 10
//Given explicitly since brackets block recursive macro expansion
//Sure that a couple more wraps can be derived from STARTVAL, but I'm too sleepy
#define NUMIT 11 //TODO
#define P00_DPRTINT(NAME, X, I) printf("%d\n",NAME-I)
int main(void) {
P99_FOR(STARTVAL, NUMIT, P00_SEP, P00_DPRTINT);
return 0;
}
p99 library
В нем ответ:
1. Есть основная программа и есть функция печати, где можно делать, что угодно;
2. Ограничений на язык нет;
3. Я бы приравнял исключение возбуждаемое делением на 0 к неявному сравнению, выполняемому ЦПУ аппаратно.
В целом задача на сообразительность, не на знание языка, просто посмотреть как человек мыслит, что делает в нестандартной ситуации в сложной ситуации, работает ли на результат.
1. вместо рекурсии — порождать новый процесс, передавать ему хендл на ту же консоль, а текущий процесс завершать
2. примерно то же самое можно попробовать с потоками, но я чет зафейлил задачу по join-у thread из следующего в цепочке (но вроде формально это не запрещено).
Т.о. цикл будет за счет средств порождения нового потока/процесса.
Хз насколько это соответствует условию «нет скрытых условий», ибо в ОС этих условий дофига, но в коде программы вроде как и нет
typedef void (*func_t)(void);
void fex(){printf("0\n"); exit(0);}
void fvd(){}
int main(){
func_t arr[2] = {fex, fvd};
int n=10;
while(1){
printf("%i ", n);
n--;
arr[!!n]();
}
printf("\n");
}
Вариант с циклом без проверок:
int main(){
int n=10;
while(1){
printf("%i ", n);
n--;
n || ({break;n=0;});
}
}
#include "stdio.h"
int print_number(int n)
{
printf("%d ", n);
return (n && print_number(n - 1));
}
int main(void)
{
return print_number(100000);
}
int ex()
{
exit(0);
}
int main()
{
int i = 1000;
while(1)
{
printf("%d\n", i);
i || ex();
i --;
}
return 0;
}
Зачтется?
#include <iostream>
typedef void(*Func)(int);
void printValue(int32_t value) {
std::cout << value << std::endl;
}
void end(int32_t value) {
exit(0);
}
int main()
{
int32_t N = 100;
Func funcs[] = {printValue, end};
begin:
funcs[((uint32_t)N) >> 31](N);
N--;
goto begin;
}
void printntoz( unsigned int n )
{
Repeat:
printf("%u ", n);
n--;
switch( n >> sizeof(int) * 8 - 1 )
{ case 0: goto Repeat;
case 1: goto End;
}
End:{}
}
int main() {
int n = 1234;
while(n)
printf("%d\n", n--);
printf("0");
return 0;
}
Интересная задачка на С