Ciekawe zastosowanie operatorów

4

Bawiłem się przed chwilą operatorami i wpadłem na ciekawy pomysł, aby zrealizować coś takiego, żeby możliwe było zapisanie kodu w ten sposób i działał on poprawnie:

#include <iostream>
#include "mojeoperatory.h"
int _silnia(int n)
{
  int wynik = 1;
  for (int i=1; i<=n; i++)
    wynik*=i;
  return wynik;
}

int _kwadrat(int n)
{
  return n*n;
}

int _potega(int a, int b)
{
  int c = 1;
  for (int i=0; i<b; i++)
    c*=a;
  return c;
}

#define silnia zprawej(int,_silnia)
#define kwadrat zlewej(int,_kwadrat)
#define potega zobu(int,_potega)

using namespace std;

int main()
{
  int s = 5 silnia;
  int k = kwadrat 3;
  int p = 2 potega 3;
  cout << s << endl;
  cout << k << endl;
  cout << p << endl;
  return 0;
}

i udało mi się :P

oto jak wygląda "magiczny" plik mojeoperatory.h:

template<class T>
struct __tmp1__
{
  __tmp1__(T (*f)(T)): f(f) {}
  T (*f)(T);
  T operator()(T a) { return f(a); }
};

template<class T>
struct __tmp2__
{
  __tmp2__(T (*f)(T,T)): f(f) {}
  T b;
  T (*f)(T,T);
  T operator()(T a) { return f(a,b); }
};

template<class T>
T operator>> (T a, __tmp1__<T> __tmp1__)
{
  return __tmp1__(a);
}

template<class T>
T operator<< (__tmp1__<T> __tmp1__, T a)
{
  return __tmp1__(a);
}

template<class T>
T operator>> (T a, __tmp2__<T> __tmp1__)
{
  return __tmp1__(a);
}

template<class T>
__tmp2__<T> operator<< (__tmp2__<T> __tmp1__, T a)
{
  __tmp1__.b = a;
  return __tmp1__;
}

#define zprawej(typ,funkcja) >> __tmp1__<typ>(funkcja)
#define zlewej(typ,funkcja) __tmp1__<typ>(funkcja) <<
#define zobu(typ,funkcja) >> __tmp2__<typ>(funkcja) <<

nie jest to może zbyt skomplikowane, ale zastosowanie wydaje mi się dość ciekawe :P

0

ponazywałeś wszystko tak, żeby nie można było się rozczytać. na przykład __tmp1__ raz jest typem, a raz zmienną typu __tmp2__.

potęga była źle (sprawdzałem pod VC++ 2010).

template<class T>
struct unary_operator
{
  typedef T (*func_type)(T);
  func_type func;
  unary_operator(func_type f): func(f) {}
  T operator()(T a) { return func(a); }
};
 
template<class T>
T operator>> (T left, unary_operator<T> right)
{
  return right(left);
}
 
template<class T>
T operator<< (unary_operator<T> left, T right)
{
  return left(right);
}

#define zprawej(typ,funkcja) >> unary_operator<typ>(funkcja)
#define zlewej(typ,funkcja) unary_operator<typ>(funkcja) <<

template<class T>
struct binary_operator
{
  typedef T (*func_type)(T,T);
  T a;
  func_type func;
  binary_operator(func_type f): func(f) {}
  T operator()(T b) { return func(a,b); }
};
 
template<class T>
binary_operator<T> operator>> (T left, binary_operator<T> right)
{
  right.a = left;
  return right;
}
 
template<class T>
T operator<< (binary_operator<T> left, T right)
{
  return left(right);
}
 
#define zobu(typ,funkcja) >> binary_operator<typ>(funkcja) <<
1

osobiście uważam takie rozwiązania za błędogenne.
Przykład: priorytety operatorów w twoim rozwiązaniu nie są zgodne z oczekiwaniem.

cout << 2 silnia; // to chyba się nawet nie skompiluje
int s = 3 * 2 potega 3; // daje zły wynik

Lepiej przeciążyć operatory o wysokim priorytecie.
Dodatkowo twoje rozwiązanie kiepsko obsługuje typy! Co jeśli chcę liczyć potęgę z liczby zmiennoprzecinkowej? Zły wynik bez ostrzeżeń.

Wszystkie te problemy można rozwiązać, więc pomęcz się z tym jeszcze :P

0

trudno będzie rozwiązać problem priorytetów, bo „nie wiadomo” jaki priorytet miałoby mieć tworzone właśnie działanie.
przykładowo: chciałbym operator kiszenie o priorytecie słabszym od dodawania, oraz ugniatanie o mocniejszym niż mnożenie ale słabszym od potega.

chyba żeby ograniczyć się do listy kilku dobrze działających operatorów (potega, silnia) bez możliwości szybkiego tworzenia „własnych”.

albo (rozwiązanie pośrednie) do wyboru kilka ustalonych priorytetów (np. additive — priorytet dodawania/odejmowania, multiplicative — mnożenia/dzielenia)

0

Uznanie za chęć ogarnięcia i przemyślenia tego co zrobiłeś. Przyznam szczerze że bardzo ciężko by mi było z szablonami(rzadko z nimi pracuję), więc sporo by to czasu kosztowało. Jedno jest tylko mało fajne - makra. W C++ trzeba uciekać od #define i innych rzeczy z preprocesora - w prywatnym projekcie można tak się bawić ale w czymś większym jest to ryzykowne.

0

to było zrobione tylko dla testów, po prostu zastanawiałem się czy takie coś jest możliwe do napisania. Na pewno znajdzie się zawsze coś co spowoduje, że coś zadziała nie tak jak powinno. Miało to działać tylko na prostych operacjach. Na ten pomysł wpadłem, jak zacząłem bawić się definami, żeby zrobić coś w stylu nakładki na C++, która by pozwoliła pisać w naturalnym języku. Tego nie udało mi się do końca zrobić tak jak planowałem, ale przy okazji zrobiłem to co widać :)

0

@Wszyscy wyżej, nie wiem gdzie widzicie problem, akurat postawienie nawiasu załatwia sprawę priorytetów, a nie jest to wiele pisania.
@Hostel, ale gadasz głupoty. Słyszałeś kiedyś o metaprogramowaniu? Tam do tego także używa się makr. A jest to jedno z najważniejszych rzeczy przy pisaniu gier komputerowych, bo działa w czasie kompilacji a nie w czasie działania programu.
Przykład:

template<unsigned N>
struct _Fib_
{
    enum { Val = _Fib_<N - 1>::Val + _Fib_<N - 2>::Val };
};

template<> _Fib_<0> { enum { Val = 0 }; };
template<> _Fib_<1> { enum { Val = 1 }; };

//Użycie makra
#define FibT( n ) _Fib_< n >::Val

//Wykorzystanie w kodzie
cout << FibT(5);

Co do krwq, to gratuluję, zastanawiałem się zawsze, czy można stworzyć własne operatory, bo zawsze można wymyślić jakiś ciąg działań i nazwać go np @. Dzięki, że wrzuciłeś to na forum ;]

0
MJay napisał(a)

@Hostel, ale gadasz głupoty. Słyszałeś kiedyś o metaprogramowaniu? Tam do tego także używa się makr. A jest to jedno z najważniejszych rzeczy przy pisaniu gier komputerowych, bo działa w czasie kompilacji a nie w czasie działania programu.

C++ jest słabe jeśli chodzi o metaprogramowanie - taka jest prawda. W grach jak już to wykorzystuje się języki skryptowe(np. lua), C++ jedynie wczytuje i odpala skrypt. Jeśli wtedy mamy metaprogramowanie to za pomocą skryptów bo C++ jako tako nie wspiera metaprogramowania. W Qt np. zakodowali metaprogramowanie w środku- możesz zmieniać właściwości obiektu podczas wykonywania. Sam szablon i definicja makro bym nie stosował jako metaprogramowanie w przypadku gdy ktoś może np. nie wiedzieć co dane makro robi i wstawi bubla. Tego typu makra są dobre jedynie by zakodować bebechy do których nikt nie będzie zaglądał. Tak więc wcale nie uważam bym pisał głupoty. W każdej książce i każdy doświadczony programista powie że makra trzeba omijać, chyba że lubimy rwać włosy i ślęczeć w nocy z debuggerem.

@krwq
Mam nadzieję że nie odbierasz moich wypowiedzi negatywnie. Uważam że zrobiłeś fajne ćwiczenie i że to dobra nauka.

Edyta dopisała:
Wracając do metaprogramowania i szablonów - przekombinowanie szablonu, lub błędne użycie(gdy się w dodatku "wie" i "ma rację" że się dobrze użyło) powoduje litanię kompilatora z której bez słownika języka nordyckich bogów nie da się dojść o co chodzi.

0

Zapomniałem dopisać struct:

template <> struct _Fib_< 0 > { enum { Val = 0 }; };
template <> struct _Fib_< 1 > { enum { Val = 1 }; };

Teraz już nie będzie litanii, tylko piękna liczba równa 120.

0

A da się tak zrobić z "obiektami"?

1 użytkowników online, w tym zalogowanych: 0, gości: 1