Funzioni lambda ricorsive in C ++ 11


143

Sono nuovo di C ++ 11. Sto scrivendo la seguente funzione lambda ricorsiva, ma non viene compilata.

sum.cpp

#include <iostream>
#include <functional>

auto term = [](int a)->int {
  return a*a;
};

auto next = [](int a)->int {
  return ++a;
};

auto sum = [term,next,&sum](int a, int b)mutable ->int {
  if(a>b)
    return 0;
  else
    return term(a) + sum(next(a),b);
};

int main(){
  std::cout<<sum(1,10)<<std::endl;
  return 0;
}

errore di compilazione:

vimal @ linux-718q: ~ / Study / 09C ++ / c ++ 0x / lambda> g ++ -std = c ++ 0x sum.cpp

sum.cpp: nella funzione lambda: sum.cpp: 18: 36: errore: ' ((<lambda(int, int)>*)this)-><lambda(int, int)>::sum' non può essere usato come funzione

versione gcc

versione gcc 4.5.0 20091231 (sperimentale) (GCC)

Ma se cambio la dichiarazione sum()come di seguito, funziona:

std::function<int(int,int)> sum = [term,next,&sum](int a, int b)->int {
   if(a>b)
     return 0;
   else
     return term(a) + sum(next(a),b);
};

Qualcuno potrebbe far luce su questo?


Potrebbero essere dichiarazioni statiche contro implicitamente dinamiche?
Hamish Grubijan,

3
Cosa mutableci fa la parola chiave lì?
Saluti e hth. - Alf

L'acquisizione di variabili con durata di memorizzazione non automatica non è consentita. Dovresti farlo in questo modo: chat.stackoverflow.com/transcript/message/39298544#39298544
Euri Pinhollow,

Solo un FYI, nel tuo secondo frammento di codice il tuo lambda è troppo dettagliato, considera questo cambiamento:std::function<int(int,int)> sum = [&](int a, int b) {
armanali

Risposte:


189

Pensa alla differenza tra la versione automatica e la versione del tipo completamente specificato. La parola chiave auto ricava il suo tipo da qualunque cosa sia inizializzata, ma ciò con cui lo stai inizializzando deve sapere quale sia il suo tipo (in questo caso, la chiusura lambda deve conoscere i tipi che sta catturando). Qualcosa di un problema di pollo e uova.

D'altra parte, il tipo di un oggetto funzione completamente specificato non ha bisogno di "sapere" nulla di ciò che gli viene assegnato, e quindi anche la chiusura del lambda può essere pienamente informata sui tipi che sta catturando.

Considera questa leggera modifica del tuo codice e potrebbe avere più senso:

std::function<int(int,int)> sum;
sum = [term,next,&sum](int a, int b)->int {
if(a>b)
    return 0;
else
    return term(a) + sum(next(a),b);
};

Ovviamente, questo non funzionerebbe con l' auto . Le funzioni lambda ricorsive funzionano perfettamente (almeno in MSVC, dove ho esperienza con loro), è solo che non sono realmente compatibili con l'inferenza di tipo.


3
Non sono d'accordo con questo. Il tipo di lambda è ben noto non appena viene inserito il corpo della funzione - non c'è motivo per cui non dovrebbe essere dedotto da allora.
Cucciolo

16
@DeadMG ma le specifiche vietano di fare riferimento alla autovariabile nell'inizializzatore di essa. il tipo della variabile automatica non è ancora noto durante l'elaborazione dell'inizializzatore.
Johannes Schaub - litb

1
Ti chiedi perché questo non sia contrassegnato come "risposta" e che Python uno sia classificato come "Risposta" ?!
Ajay,

1
@Puppy: nel caso di una cattura implicita, tuttavia, per efficienza vengono effettivamente catturate solo le variabili di riferimento, quindi il corpo deve essere analizzato.
kec,

Esiste un'interpretazione valida per sumaltro std::function<int(int, int)>o le specifiche C ++ non si sono prese la briga di inferirle?
Mateen Ulhaq,

79

Il trucco è alimentare l'implementazione lambda su se stesso come parametro , non mediante acquisizione.

const auto sum = [term,next](int a, int b) {
  auto sum_impl=[term,next](int a,int b,auto& sum_ref) mutable {
    if(a>b){
      return 0;
    }
    return term(a) + sum_ref(next(a),b,sum_ref);
  };
  return sum_impl(a,b,sum_impl);
};

Tutti i problemi di informatica possono essere risolti da un altro livello di riferimento indiretto . Ho trovato questo semplice trucco su http://pedromelendez.com/blog/2015/07/16/recursive-lambdas-in-c14/

Essa non necessita di C ++ 14, mentre la domanda è in C ++ 11, ma forse interessante per la maggior parte.

Il passaggio tramite std::functionè anche possibile, ma può comportare un codice più lento. Ma non sempre. Dai un'occhiata alle risposte a std :: function vs template


Questa non è solo una peculiarità del C ++, è direttamente mappata alla matematica del calcolo lambda. Da Wikipedia :

Lambda calculus cannot express this as directly as some other notations:
all functions are anonymous in lambda calculus, so we can't refer to a
value which is yet to be defined, inside the lambda term defining that
same value. However, recursion can still be achieved by arranging for a
lambda expression to receive itself as its argument value

3
Questo sembra molto peggio che usare esplicitamente function<>. Non riesco a capire perché qualcuno lo preferisca. Modifica: apparentemente è più veloce.
Timmmm,

17
questo è molto meglio di std :: function per 3 motivi: non richiede la cancellazione del tipo o l'allocazione della memoria, può essere constexpr e funziona correttamente con i parametri (modello) automatici / tipo di ritorno
Ivan Sanz-Carasa,

3
Presumibilmente questa soluzione ha anche il vantaggio di essere copiabile senza che il riferimento std :: function esca dall'ambito?
Uri Granta,

3
Hm, quando ci provava, GCC 8.1 (linux) si lamentava: error: use of ‘[...]’ before deduction of ‘auto’- necessario specificare esplicitamente il tipo di ritorno (d'altra parte, non aveva bisogno di essere mutabile).
Aconcagua,

@Aconcagua stesso qui con Xcode10 e ho impostato lo standard C ++ su 17 pari
IceFire

39

Con C ++ 14, ora è abbastanza facile creare un lambda ricorsivo efficiente senza dover sostenere l'overhead aggiuntivo di std::function, in poche righe di codice (con una piccola modifica dall'originale per impedire all'utente di prendere una copia accidentale ):

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // [edit: Barry] pass in std::ref(*this) instead of *this
        return f(std::ref(*this), std::forward<Args>(args)...);
    }
};

// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}

con cui il tuo sumtentativo originale diventa:

auto sum = make_y_combinator([term,next](auto sum, int a, int b) {
  if (a>b) {
    return 0;
  }
  else {
    return term(a) + sum(next(a),b);
  }
});

In C ++ 17, con CTAD, possiamo aggiungere una guida alla detrazione:

template <class F> y_combinator(F) -> y_combinator<F>;

Ciò ovvia alla necessità della funzione di aiuto. Possiamo semplicemente scrivere y_combinator{[](auto self, ...){...}}direttamente.


In C ++ 20, con CTAD per aggregati, la guida alla detrazione non sarà necessaria.


Questo è fantastico, ma si potrebbe considerare std::forward<decltype(sum)>(sum)anziché sumsull'ultima riga.
Johan Lundberg,

@Johan No, ce n'è solo uno operator()quindi non c'è niente da guadagnare inoltrandosum
Barry il

Oh, è vero. Non utilizzato per utilizzare i riferimenti di inoltro senza inoltro.
Johan Lundberg,

Il combinatore Y è sicuramente la strada da percorrere. Ma dovresti davvero aggiungere un non- constoverload nel caso in cui l'oggetto funzione fornito abbia un constoperatore non -call. E usa SFINAE e calcolato noexceptper entrambi. Inoltre, non è più necessaria la funzione maker in C ++ 17.
Deduplicatore,

2
@minex Sì, auto sumcopia ... ma copia a reference_wrapper, che è la stessa cosa di prendere un riferimento. Farlo una volta nell'implementazione significa che nessuno degli usi verrà mai accidentalmente copiato.
Barry,

22

Ho un'altra soluzione, ma lavoro solo con lambda apolidi:

void f()
{
    static int (*self)(int) = [](int i)->int { return i>0 ? self(i-1)*i : 1; };
    std::cout<<self(10);
}

Il trucco qui è che lambda può accedere a variabili statiche e puoi convertire quelle senza stato in puntatore a funzione.

Puoi usarlo con lambda standard:

void g()
{
    int sum;
    auto rec = [&sum](int i) -> int
    {
        static int (*inner)(int&, int) = [](int& _sum, int i)->int 
        {
            _sum += i;
            return i>0 ? inner(_sum, i-1)*i : 1; 
        };
        return inner(sum, i);
    };
}

Il suo lavoro in GCC 4.7


3
Questo dovrebbe avere prestazioni migliori rispetto a std :: function, quindi +1 per l'alternativa. Ma davvero, a questo punto mi chiedo se usare lambdas sia l'opzione migliore;)
Antoine,

Se hai un lambda apolide puoi anche renderlo completamente funzionante.
Timmmm,

1
@Timmmm Ma poi perdi parte dell'implementazione in una parola esterna, di solito i lambda sono strettamente associati alla funzione genitore (anche se senza acquisizioni). In caso contrario, non dovresti usare lambda in primo luogo e utilizzare le normali funzioni dei funzioni.
Yankes,

10

È possibile effettuare una chiamata lambda stessa in modo ricorsivo. L'unica cosa che devi fare è fare riferimento a esso tramite un wrapper di funzione in modo che il compilatore sappia che è di ritorno e tipo di argomento (non puoi catturare una variabile - la stessa lambda - che non è stata ancora definita) .

  function<int (int)> f;

  f = [&f](int x) {
    if (x == 0) return 0;
    return x + f(x-1);
  };

  printf("%d\n", f(10));

Fare molta attenzione a non esaurire l'ambito dell'involucro f.


3
Ma questo è identico alla risposta accettata e potrebbe avere una penalità per l'uso della funzione std.
Johan Lundberg,

9

Per rendere ricorsivo lambda senza usare classi e funzioni esterne (come std::functiono combinatore a virgola fissa) si può usare la seguente costruzione in C ++ 14 ( esempio live ):

#include <utility>
#include <list>
#include <memory>
#include <iostream>

int main()
{
    struct tree
    {
        int payload;
        std::list< tree > children = {}; // std::list of incomplete type is allowed
    };
    std::size_t indent = 0;
    // indication of result type here is essential
    const auto print = [&] (const auto & self, const tree & node) -> void
    {
        std::cout << std::string(indent, ' ') << node.payload << '\n';
        ++indent;
        for (const tree & t : node.children) {
            self(self, t);
        }
        --indent;
    };
    print(print, {1, {{2, {{8}}}, {3, {{5, {{7}}}, {6}}}, {4}}});
}

stampe:

1
 2
  8
 3
  5
   7
  6
 4

Nota, il tipo di risultato di lambda deve essere specificato esplicitamente.


6

Ho eseguito un benchmark confrontando una funzione ricorsiva rispetto a una funzione lambda ricorsiva utilizzando il std::function<>metodo di acquisizione. Con le ottimizzazioni complete abilitate su clang versione 4.1, la versione lambda è stata notevolmente più lenta.

#include <iostream>
#include <functional>
#include <chrono>

uint64_t sum1(int n) {
  return (n <= 1) ? 1 : n + sum1(n - 1);
}

std::function<uint64_t(int)> sum2 = [&] (int n) {
  return (n <= 1) ? 1 : n + sum2(n - 1);
};

auto const ITERATIONS = 10000;
auto const DEPTH = 100000;

template <class Func, class Input>
void benchmark(Func&& func, Input&& input) {
  auto t1 = std::chrono::high_resolution_clock::now();
  for (auto i = 0; i != ITERATIONS; ++i) {
    func(input);
  }
  auto t2 = std::chrono::high_resolution_clock::now();
  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
  std::cout << "Duration: " << duration << std::endl;
}

int main() {
  benchmark(sum1, DEPTH);
  benchmark(sum2, DEPTH);
}

Produce risultati:

Duration: 0 // regular function
Duration: 4027 // lambda function

(Nota: ho anche confermato con una versione che ha preso gli input da cin, in modo da eliminare la valutazione del tempo di compilazione)

Clang produce anche un avviso del compilatore:

main.cc:10:29: warning: variable 'sum2' is uninitialized when used within its own initialization [-Wuninitialized]

Quale è previsto e sicuro, ma dovrebbe essere notato.

È fantastico avere una soluzione nelle nostre cinture degli strumenti, ma penso che il linguaggio avrà bisogno di un modo migliore per gestire questo caso se le prestazioni devono essere paragonabili ai metodi attuali.

Nota:

Come ha sottolineato un commentatore, sembra che l'ultima versione di VC ++ abbia trovato un modo per ottimizzarlo al punto di pari prestazioni. Forse non abbiamo bisogno di un modo migliore per gestirlo, dopo tutto (tranne per lo zucchero sintattico).

Inoltre, come alcuni altri post SO hanno delineato nelle ultime settimane, le prestazioni di per std::function<>sé possono essere la causa del rallentamento rispetto alla funzione di chiamata direttamente, almeno quando l'acquisizione lambda è troppo grande per adattarsi ad alcuni std::functionusi dello spazio ottimizzati per le librerie per piccoli funzioni (Suppongo che in qualche modo piacciano le varie ottimizzazioni di stringhe corte?).


2
-1. Si noti che l'unico motivo per cui la versione "lambda" impiega più tempo è perché lo si associa a una funzione std ::, che fa chiamare l'operatore () una chiamata virtuale e che ovviamente richiederebbe più tempo. Inoltre, il codice, in modalità di rilascio VS2012, ha richiesto la stessa quantità di tempo in entrambi i casi.
Yam Marcovic,

@YamMarcovic Cosa? Questo è attualmente l'unico modo noto per scrivere un lambda ricorsivo (quello era il punto dell'esempio). Sono molto lieto di sapere che VS2012 ha trovato un modo per ottimizzare questo caso d'uso (anche se recentemente ci sono stati più sviluppi su questo argomento, apparentemente se il mio lambda avesse catturato di più non si sarebbe adattato alla norma std :: function small- ottimizzazioni di funzioni di memoria o quant'altro).
mmocny,

2
Riconosciuto. Ho frainteso il tuo post. +1 allora. Gah, puoi votare solo se modifichi questa risposta. Quindi potresti enfatizzarlo un po 'di più, come nel commento?
Yam Marcovic,

1
@YamMarcovic Done. Apprezzo la tua disponibilità a fornire feedback e perfezionarlo quando necessario. +1 a te, buon signore.
mmocny,

0 tempo di solito significa "l'intera operazione è stata ottimizzata". Prendere input da cin non fa nulla se il compilatore dimostra che non fai nulla con il risultato del tuo calcolo.
Yakk - Adam Nevraumont,

1

Questa è un'implementazione leggermente più semplice dell'operatore fixpoint che rende un po 'più ovvio esattamente cosa sta succedendo.

#include <iostream>
#include <functional>

using namespace std;

template<typename T, typename... Args>
struct fixpoint
{
    typedef function<T(Args...)> effective_type;
    typedef function<T(const effective_type&, Args...)> function_type;

    function_type f_nonr;

    T operator()(Args... args) const
    {
        return f_nonr(*this, args...);
    }

    fixpoint(const function_type& p_f)
        : f_nonr(p_f)
    {
    }
};


int main()
{
    auto fib_nonr = [](const function<int(int)>& f, int n) -> int
    {
        return n < 2 ? n : f(n-1) + f(n-2);
    };

    auto fib = fixpoint<int,int>(fib_nonr);

    for (int i = 0; i < 6; ++i)
    {
        cout << fib(i) << '\n';
    }
}

Penso che potresti migliorare la tua risposta (per quanto riguarda le prestazioni) se sostituisci std::functioncon il puntatore a funzione (dei core funzionerà solo con la funzione normale e lambdas senza stato). Btw fib_nonrdovrebbe accettare fixpoint<int,int>, se lo usi std::functionrichiede la creazione di una nuova copia da *this.
Yankes

1

Ecco una versione raffinata della soluzione combinatore Y basata su quella proposta da @Barry.

template <class F>
struct recursive {
  F f;
  template <class... Ts>
  decltype(auto) operator()(Ts&&... ts)  const { return f(std::ref(*this), std::forward<Ts>(ts)...); }

  template <class... Ts>
  decltype(auto) operator()(Ts&&... ts)  { return f(std::ref(*this), std::forward<Ts>(ts)...); }
};

template <class F> recursive(F) -> recursive<F>;
auto const rec = [](auto f){ return recursive{std::move(f)}; };

Per usarlo, si potrebbe fare quanto segue

auto fib = rec([&](auto&& fib, int i) {
// implementation detail omitted.
});

È simile alla let recparola chiave in OCaml, sebbene non sia la stessa.


0

C ++ 14: Ecco un insieme generico anonimo ricorsivo anonimo / nessuna acquisizione di lambda che genera tutti i numeri da 1, 20

([](auto f, auto n, auto m) {
    f(f, n, m);
})(
    [](auto f, auto n, auto m) -> void
{
    cout << typeid(n).name() << el;
    cout << n << el;
    if (n<m)
        f(f, ++n, m);
},
    1, 20);

Se ho capito bene questo sta usando la soluzione combinatore a Y.

Ed ecco la versione sum (n, m)

auto sum = [](auto n, auto m) {
    return ([](auto f, auto n, auto m) {
        int res = f(f, n, m);
        return res;
    })(
        [](auto f, auto n, auto m) -> int
        {
            if (n > m)
                return 0;
            else {
                int sum = n + f(f, n + 1, m);
                return sum;
            }
        },
        n, m); };

auto result = sum(1, 10); //result == 55

-1

Ecco la risposta finale per il PO. Ad ogni modo, Visual Studio 2010 non supporta l'acquisizione di variabili globali. E non è necessario acquisirli perché la variabile globale è accessibile a livello globale per definizione. La seguente risposta utilizza invece la variabile locale.

#include <functional>
#include <iostream>

template<typename T>
struct t2t
{
    typedef T t;
};

template<typename R, typename V1, typename V2>
struct fixpoint
{
    typedef std::function<R (V1, V2)> func_t;
    typedef std::function<func_t (func_t)> tfunc_t;
    typedef std::function<func_t (tfunc_t)> yfunc_t;

    class loopfunc_t {
    public:
        func_t operator()(loopfunc_t v)const {
            return func(v);
        }
        template<typename L>
        loopfunc_t(const L &l):func(l){}
        typedef V1 Parameter1_t;
        typedef V2 Parameter2_t;
    private:
        std::function<func_t (loopfunc_t)> func;
    };
    static yfunc_t fix;
};
template<typename R, typename V1, typename V2>
typename fixpoint<R, V1, V2>::yfunc_t fixpoint<R, V1, V2>::fix = [](tfunc_t f) -> func_t {
    return [f](fixpoint<R, V1, V2>::loopfunc_t x){  return f(x(x)); }
    ([f](fixpoint<R, V1, V2>::loopfunc_t x) -> fixpoint<R, V1, V2>::func_t{
        auto &ff = f;
        return [ff, x](t2t<decltype(x)>::t::Parameter1_t v1, 
            t2t<decltype(x)>::t::Parameter1_t v2){
            return ff(x(x))(v1, v2);
        }; 
    });
};

int _tmain(int argc, _TCHAR* argv[])
{
    auto term = [](int a)->int {
      return a*a;
    };

    auto next = [](int a)->int {
      return ++a;
    };

    auto sum = fixpoint<int, int, int>::fix(
    [term,next](std::function<int (int, int)> sum1) -> std::function<int (int, int)>{
        auto &term1 = term;
        auto &next1 = next;
        return [term1, next1, sum1](int a, int b)mutable ->int {
            if(a>b)
                return 0;
        else
            return term1(a) + sum1(next1(a),b);
        };
    });

    std::cout<<sum(1,10)<<std::endl; //385

    return 0;
}

È possibile rendere agnostico questo compilatore di risposte?
Rayryeng

-2

Stai provando a catturare una variabile (somma) che sei nel mezzo della definizione. Non può essere buono.

Non penso che sia davvero possibile ricorrere a lambda C ++ 0x ricorsive. Dovresti essere in grado di catturare altri lambda, però.


3
ma funziona se la dichiarazione di somma viene cambiata da 'auto' a std :: function <int (int, int)> senza cambiare la lista di acquisizione.
weima,

Perché allora non è più una lambda, ma una funzione che può essere utilizzata al posto della lambda?
Hamish Grubijan,

-2

Questa risposta è inferiore a quella di Yankes, ma comunque eccola:

using dp_type = void (*)();

using fp_type = void (*)(dp_type, unsigned, unsigned);

fp_type fp = [](dp_type dp, unsigned const a, unsigned const b) {
  ::std::cout << a << ::std::endl;
  return reinterpret_cast<fp_type>(dp)(dp, b, a + b);
};

fp(reinterpret_cast<dp_type>(fp), 0, 1);

Penso che dovresti evitare reinterpret_cast. Probabilmente il modo migliore nel tuo caso è creare una struttura che sostituisca dp_type. Dovrebbe avere un campo fp_type, può essere costruito da fp_typee avere un operatore ()con argomenti simili fp_type. Questo sarà vicino std::functionma consentirà l'argomento autoreferenziale.
Yankes,

Volevo pubblicare un esempio minimo, senza una struttura, sentiti libero di modificare la mia risposta e fornire una soluzione più completa. A structaggiungerebbe anche un ulteriore livello di riferimento indiretto. L'esempio funziona e il cast è conforme agli standard, non so a cosa -1servisse.
user1095108

no, struct funzionerà solo come contenitore per il puntatore e verrà passato come valore. Questo non sarà più indiretto o sovraccarico del puntatore. E circa -1non sapevo chi te lo regalasse, ma penso che sia perché reinterpret_castdovrebbe essere usato come ultima risorsa.
Yankes,

Si castsuppone che il funzionamento dello standard c ++ 11 sia garantito. L'uso di un struct, ai miei occhi, potrebbe sconfiggere l'uso di un oggetto lambda. Dopotutto, quello structche proponi è un functor, usando un oggetto lambda.
user1095108,

Guarda la soluzione @Pseudonym, rimuovi solo std::functione avrai qualcosa di simile a quello che avevo in mente. Questo probabilmente avrà prestazioni simili alla tua soluzione.
Yankes

-3

È necessario un combinatore a punto fisso. Vedi questo .

o guarda il seguente codice:

//As decltype(variable)::member_name is invalid currently, 
//the following template is a workaround.
//Usage: t2t<decltype(variable)>::t::member_name
template<typename T>
struct t2t
{
    typedef T t;
};

template<typename R, typename V>
struct fixpoint
{
    typedef std::function<R (V)> func_t;
    typedef std::function<func_t (func_t)> tfunc_t;
    typedef std::function<func_t (tfunc_t)> yfunc_t;

    class loopfunc_t {
    public:
        func_t operator()(loopfunc_t v)const {
            return func(v);
        }
        template<typename L>
        loopfunc_t(const L &l):func(l){}
        typedef V Parameter_t;
    private:
        std::function<func_t (loopfunc_t)> func;
    };
    static yfunc_t fix;
};
template<typename R, typename V>
typename fixpoint<R, V>::yfunc_t fixpoint<R, V>::fix = 
[](fixpoint<R, V>::tfunc_t f) -> fixpoint<R, V>::func_t {
    fixpoint<R, V>::loopfunc_t l = [f](fixpoint<R, V>::loopfunc_t x) ->
        fixpoint<R, V>::func_t{
            //f cannot be captured since it is not a local variable
            //of this scope. We need a new reference to it.
            auto &ff = f;
            //We need struct t2t because template parameter
            //V is not accessable in this level.
            return [ff, x](t2t<decltype(x)>::t::Parameter_t v){
                return ff(x(x))(v); 
            };
        }; 
        return l(l);
    };

int _tmain(int argc, _TCHAR* argv[])
{
    int v = 0;
    std::function<int (int)> fac = 
    fixpoint<int, int>::fix([](std::function<int (int)> f)
        -> std::function<int (int)>{
        return [f](int i) -> int{
            if(i==0) return 1;
            else return i * f(i-1);
        };
    });

    int i = fac(10);
    std::cout << i; //3628800
    return 0;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.