Kirjautuminen

Haku

Tehtävät

Koodit: C++: Laskeminen käännöksen aikana templateilla

Kirjoittaja: Weggo

Kirjoitettu: 11.07.2012 – 11.07.2012

Tagit: algoritmit, ohjelmointitavat, koodi näytille, vinkki

Innostuin käännösaikaisesta koodin tuottamisesta (template metaprogramming) ja tein pienen koodipätkän, joka laskee Fibonaccin lukuja ja alkulukuja ohjelman kääntämisen aikana.

Käännösaikanen koodi on Turing-täydellinen, joten sillä on mahdollista kirjoittaa laskennallisesti monimutkaisempiakin algoritmeja.

Käännösaikaisen koodin tuottaminen

Käännösaikaisen koodin tuottaminen on hieman mutkikkaampaa, sillä kaikki arvot ovat muuttumattomia(=immutable). Tämän takia ainoa tapa arvojen muuttamiselle on rekursiivisesti toimivat algoritmit, jotka ottavat template-parametreja ja syöttävät ne uudestaan samaan algoritmiin(=rekursio). Rekursio loppuu, kun template-struktuurin spesialisaatio luodaan tämän normaalin template-struktuurin sijaan.

Ohjelman toiminta

Ohjelman käännösaikaiset algoritmit käyttävät rekursiota, ja muokkaavat tarvittavia parametreja jokaisen 'instanssin' aikana. Fibonacci-algoritmi laskee Fibonaccin luvut sen määritellyn algoritmin mukaan. Alkuluku-algoritmi laskee suurimman alkuluvun annetun parametrin arvoa edeltävän luvun sisällä.

Koodi

#include <iostream>
using namespace std;

// kertoma-algoritmi; huomaa: ei toimi suurilla luvuilla
template<size_t N>
struct factorial
{
	enum {VAL = N * factorial<N - 1>::VAL};
};
template<>
struct factorial<0>
{
	enum {VAL = 1};
};

// jos N+1 on alkuluku, VAL = N+1, muutoin VAL = 2
template<size_t N>
struct prime_check
{
	enum {VAL = 2 + ((2 * factorial<N>::VAL) % (N + 1))};
};

// algoritmi alkuluvun löytämiselle
template<size_t N, size_t N1 = 0, size_t C = 0, size_t C1 = prime_check<C>::VAL>
struct prime
{
	enum {VAL = prime<N - 1, N1 + 1, C1, prime_check<N1>::VAL>::VAL};
};
template<size_t N, size_t N1, size_t C>
struct prime<N, N1, C, 2>
{
	enum {VAL = prime<N - 1, N1 + 1, C, prime_check<N1>::VAL>::VAL};
};
template<size_t N1, size_t C, size_t C1>
struct prime<0, N1, C, C1>
{
	enum {VAL = C};
};
template<size_t N1, size_t C>
struct prime<0, N1, C, 2>
{
	enum {VAL = C};
};

// fibonacci-algoritmi
template<size_t N>
struct fibonacci
{
	enum {VAL = fibonacci<N - 1>::VAL + fibonacci<N - 2>::VAL};
};
template<>
struct fibonacci<1>
{
	enum {VAL = 1};
};
template<>
struct fibonacci<0>
{
	enum {VAL = 0};
};

int main()
{
	const size_t input = 13;
	cout << prime<input>::VAL << endl;
	cout << fibonacci<input>::VAL << endl;
	cin.get();
	return 0;
}

Kommentit

jalski [21.07.2012 22:28:48]

#

Käännösaikaisen koodin tuottaminen on ihan mielenkiintoinen ja kiinnostava aihe.

Itse pidän PL/I:n esikääntäjästä, koska se käyttää ohjelmointikielen omaa rajoitettua syntaksia.

Esim. PL/I:n esikääntäjän avulla fibonacci lausekkeen lisääminen, mikä luo halutun kokoisen taulukon ja täyttää sen fibonaccin luvuilla:

%fibonacci: proc (n, table) statement;
  dcl n fixed;
  dcl table char;

  dcl (i, f1, f2, f3) fixed;
  dcl nums char;

  f1 = 1; f2 = 0;

  nums = '0';

  do i = 1 to n;
    f3 = f1 + f2;
    f1 = f2;
    f2 = f3;
    nums = nums || ',' || f3;
  end;

  ans('dcl ' || table || ' (0:' || n || ') fixed bin init (' || nums || ');') skip;

%end fibonacci;
%act fibonacci;

Käyttö ohjelmassa:

fibonacci n(20) table(fib);

tuossa siis n-parametri on taulukon yläindeksi ja table-parametri on haluttu nimi taulukolle.

Weggo [31.07.2012 00:38:05]

#

jalski kirjoitti:

Itse pidän PL/I:n esikääntäjästä, koska se käyttää ohjelmointikielen omaa rajoitettua syntaksia.

Kätevää, mutta kuten sanoit, kohtuu rajoitettua. Luulisin että mainitsemasi esikääntäjä pätee vain käännösaikaisten laskujen laskemiseen, kun taas C++ käsittelee pääasiassa käännösaikana määriteltyjä tyyppejä template:n avulla. C++ hyväksyy kuitenkin arvoja template määrittelyihin, jolloin template:ja voidaan käyttää moneen muuhunkin epäsuorasti.

jalski [01.08.2012 09:53:46]

#

Weggo kirjoitti:

Kätevää, mutta kuten sanoit, kohtuu rajoitettua.

Tuolla rajoitetulla tarkoitin siis lähinnä sitä, että se ei tue PL/I-kielen täyttä syntaksia vaan osaa siitä... ;-)

Weggo [01.08.2012 21:21:10]

#

Nojoo, eihän käännösaikana pystykään esim. varaamaan muistia dynaamisesti/käyttämään IO toimintoja. Mutta epäilen että PL/I-kielen käännösaikana käytettävä syntaksi ei kuitenkaan tue mitään 'laskuja' ihmeellisempiä asioita. Mielenkiintoista kuitenkin että tässä PL/I-kielessä on selvä tuki käännösaikaisille algoritmeille.

Pekka Karjalainen [13.08.2012 14:29:35]

#

C++11:n constexpr-ominaisuuden avulla tämä esimerkkikoodi olisi paljon lyhyempi. Kannattaa tutustua siihen, kunhan saa käsiinsä tarpeeksi uuden kääntäjän, joka tukee sitä.

Myöhempään Weggon kommenttiin lisäisin, että joissakin kielissä käännösaikanakin voi suorittaa ihan mielivaltaisia koodia siten, että koko kieli on käytössä ilman rajoituksia. Sellaisen ominaisuuden sisältävät kielet taitavat kuitenkin olla aika harvinaisia ja eksoottisia.

Weggo [05.09.2012 21:53:20]

#

Pekka Karjalainen kirjoitti:

C++11:n constexpr-ominaisuuden avulla tämä esimerkkikoodi olisi paljon lyhyempi.

Kyse on käännösaikaisen koodin tuottamisen esittelystä, ei laskujen laskemisesta käännösaikana.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta