Pergunta

C ++ não tem suporte nativo para avaliação preguiçosa (como Haskell faz).

Eu estou querendo saber se é possível implementar a avaliação lenta em C ++ de uma forma razoável. Se sim, como você faria isso?

EDIT:. I como a resposta de Konrad Rudolph

Eu estou querendo saber se é possível aplicá-la de uma forma mais genérica, por exemplo, utilizando uma classe parametrizada preguiçoso que, essencialmente, trabalha para T a maneira matrix_add obras para a matriz.

Qualquer operação em T voltaria preguiçoso vez. O único problema é armazenar os argumentos e código de operação dentro de si preguiçoso. qualquer um pode ver como melhorar isso?

Foi útil?

Solução

Eu estou querendo saber se é possível implementar a avaliação lenta em C ++ de uma forma razoável. Se sim, como você faria isso?

Sim, isso é possível e, muitas vezes feito, por exemplo, para os cálculos de matrizes. O principal mecanismo para facilitar esta é a sobrecarga de operador. Consideremos o caso de adição de matrizes. A assinatura da função normalmente seria algo parecido com isto:

matrix operator +(matrix const& a, matrix const& b);

Agora, para fazer esta função preguiçoso, é o suficiente para retornar um proxy em vez do resultado real:

struct matrix_add;

matrix_add operator +(matrix const& a, matrix const& b) {
    return matrix_add(a, b);
}

Agora, tudo o que precisa ser feito é para escrever este proxy:

struct matrix_add {
    matrix_add(matrix const& a, matrix const& b) : a(a), b(b) { }

    operator matrix() const {
        matrix result;
        // Do the addition.
        return result;
    }
private:
    matrix const& a, b;
};

As mentiras mágicas no operator matrix() método que é um operador de conversão implícita de matrix_add para matrix simples. Dessa forma, você pode encadear múltiplas operações (fornecendo sobrecargas adequadas, é claro). A avaliação ocorre somente quando o resultado final é atribuído a uma instância matrix.

Editar Eu deveria ter sido mais explícito. Como é, o código não faz sentido, porque embora a avaliação acontece preguiçosamente, ainda acontece na mesma expressão. Em particular, uma outra adição irá avaliar este código, a menos que a estrutura matrix_add é alterada para permitir a adição encadeada. C ++ 0x facilita muito isto permitindo modelos variádicos (isto é, molde listas de comprimento variável).

No entanto, um caso muito simples, onde este código teria realmente um real, benefício direto é o seguinte:

int value = (A + B)(2, 3);

Aqui, presume-se que A e B são matrizes bidimensionais e que dereferencing é feito em notação Fortran, ou seja, as calcula acima um elemento de uma soma matriz. É claro desperdício de adicionar toda a matrizes. matrix_add para o resgate:

struct matrix_add {
    // … yadda, yadda, yadda …

    int operator ()(unsigned int x, unsigned int y) {
        // Calculate *just one* element:
        return a(x, y) + b(x, y);
    }
};

Outros exemplos abundam. Acabei de lembrar que eu implementei algo relacionado não muito tempo atrás. Basicamente, eu tive que implementar uma classe string que deve aderir a uma interface fixa, pré-definido. No entanto, minha classe string em particular tratado com enormes cordas que não foram realmente armazenados na memória. Normalmente, o usuário seria apenas acessar pequenas substrings do string original usando uma função infix. I sobrecarregado esta função para o meu tipo de cadeia para voltar um proxy que realizada uma referência ao meu cadeia, juntamente com a entrada desejada e posição final. Só quando isso substring foi realmente utilizado foi que consultar uma API C para recuperar esta parte da string.

Outras dicas

Boost.Lambda é muito agradável, mas Boost.Proto é exatamente o que você está procurando. Ela já tem sobrecargas de todas operadores C ++, que por padrão executar a sua função habitual quando proto::eval() é chamado, mas pode ser alterado.

O que Konrad já explicado pode ser colocado ainda mais para apoiar invocações aninhados de operadores, todos executados preguiçosamente. No exemplo de Konrad, ele tem um objeto de expressão que pode armazenar exatamente dois argumentos, por exatamente dois operandos de uma operação. O problema é que ele só irá executar um subexpressão preguiçosamente, que bem explica o conceito em put avaliação preguiçosa, em termos simples, mas não melhora o desempenho substancialmente. O outro exemplo mostra também bem como se pode aplicar operator() para adicionar apenas alguns elementos usando esse objeto de expressão. Mas para avaliar expressões complexas arbitrárias, precisamos de algum mecanismo que pode loja a estrutura desse também. Não podemos dar a volta modelos para fazer isso. E o nome para isso é expression templates. A idéia é que um objeto de expressão templated pode armazenar a estrutura de alguns sub-expressão arbitrária de forma recursiva, como uma árvore, onde as operações são os nós e os operandos são os criança-nodes. Para um muito boa explicação eu só encontrei hoje (alguns dias depois que eu escrevi o código abaixo) veja aqui .

template<typename Lhs, typename Rhs>
struct AddOp {
    Lhs const& lhs;
    Rhs const& rhs;

    AddOp(Lhs const& lhs, Rhs const& rhs):lhs(lhs), rhs(rhs) {
        // empty body
    }

    Lhs const& get_lhs() const { return lhs; }
    Rhs const& get_rhs() const { return rhs; }
};

Isso vai armazenar qualquer operação de adição, mesmo um aninhada, como pode ser visto pela seguinte definição de um operador + para um tipo de ponto simples:

struct Point { int x, y; };

// add expression template with point at the right
template<typename Lhs, typename Rhs> AddOp<AddOp<Lhs, Rhs>, Point> 
operator+(AddOp<Lhs, Rhs> const& lhs, Point const& p) {
    return AddOp<AddOp<Lhs, Rhs>, Point>(lhs, p);
} 

// add expression template with point at the left
template<typename Lhs, typename Rhs> AddOp< Point, AddOp<Lhs, Rhs> > 
operator+(Point const& p, AddOp<Lhs, Rhs> const& rhs) {
    return AddOp< Point, AddOp<Lhs, Rhs> >(p, rhs);
}

// add two points, yield a expression template    
AddOp< Point, Point > 
operator+(Point const& lhs, Point const& rhs) {
    return AddOp<Point, Point>(lhs, rhs);
}

Agora, se você tiver

Point p1 = { 1, 2 }, p2 = { 3, 4 }, p3 = { 5, 6 };
p1 + (p2 + p3); // returns AddOp< Point, AddOp<Point, Point> >

Você agora só precisa operador de sobrecarga = e adicionar um construtor apropriado para o tipo de ponto e aceito operador de soma. Alterar a sua definição para:

struct Point { 
    int x, y; 

    Point(int x = 0, int y = 0):x(x), y(y) { }

    template<typename Lhs, typename Rhs>
    Point(AddOp<Lhs, Rhs> const& op) {
        x = op.get_x();
        y = op.get_y();
    }

    template<typename Lhs, typename Rhs>
    Point& operator=(AddOp<Lhs, Rhs> const& op) {
        x = op.get_x();
        y = op.get_y();
        return *this;
    }

    int get_x() const { return x; }
    int get_y() const { return y; }
};

E adicione o get_x e get_y apropriado para operador de soma como funções membro:

int get_x() const {
    return lhs.get_x() + rhs.get_x();
}

int get_y() const {
    return lhs.get_y() + rhs.get_y();
}

Observe como nós não criamos nenhum temporários do tipo Point. Poderia ter sido um grande matriz com muitos campos. Mas no momento em que é necessário o resultado, calcula-se que preguiçosamente .

Não tenho nada a acrescentar ao post de Konrad, mas você pode olhar para Eigen para um exemplo de avaliação preguiçosa feito para a direita, em um aplicativo do mundo real. É muito inspiradora.

Estou pensando em implementar uma classe de modelo, que usos std::function. A classe deve, mais ou menos, parecido com este:

template <typename Value>
class Lazy
{
public:
    Lazy(std::function<Value()> function) : _function(function), _evaluated(false) {}

    Value &operator*()  { Evaluate(); return  _value; }
    Value *operator->() { Evaluate(); return &_value; }

private:
    void Evaluate()
    {
        if (!_evaluated)
        {
            _value = _function();
            _evaluated = true;
        }
    }

    std::function<Value()> _function;
    Value _value;
    bool _evaluated;
};

Por exemplo uso:

class Noisy
{
public:
    Noisy(int i = 0) : _i(i)
    {
        std::cout << "Noisy(" << _i << ")"  << std::endl;
    }
    Noisy(const Noisy &that) : _i(that._i)
    {
        std::cout << "Noisy(const Noisy &)" << std::endl;
    }
    ~Noisy()
    {
        std::cout << "~Noisy(" << _i << ")" << std::endl;
    }

    void MakeNoise()
    {
        std::cout << "MakeNoise(" << _i << ")" << std::endl;
    }
private:
    int _i;
};  

int main()
{
    Lazy<Noisy> n = [] () { return Noisy(10); };

    std::cout << "about to make noise" << std::endl;

    n->MakeNoise();
    (*n).MakeNoise();
    auto &nn = *n;
    nn.MakeNoise();
}

código acima deve produzir a seguinte mensagem no console:

Noisy(0)
about to make noise
Noisy(10)
~Noisy(10)
MakeNoise(10)
MakeNoise(10)
MakeNoise(10)
~Noisy(10)

Note que o Noisy(10) impressão construtor não será chamado até que a variável é acessada.

Esta classe está longe de ser perfeito, no entanto. A primeira coisa seria o construtor padrão de Value terão de ser chamado na inicialização membro (impressão Noisy(0) neste caso). Podemos usar ponteiro para _value vez, mas eu não tenho certeza se isso afetaria o desempenho.

resposta Johannes' works.But quando se trata de mais parênteses, ele não funciona como desejo. Aqui está um exemplo.

Point p1 = { 1, 2 }, p2 = { 3, 4 }, p3 = { 5, 6 }, p4 = { 7, 8 };
(p1 + p2) + (p3+p4)// it works ,but not lazy enough

Porque os três sobrecarregado + operador não cobrir o caso

AddOp<Llhs,Lrhs>+AddOp<Rlhs,Rrhs>

Assim, o compilador tem de converter qualquer um (P1 + P2) ou (p3 + p4) para Point, isso não é enough.And preguiçoso quando compilador decide qual converter, ele reclama. Porque nenhum é melhor que o outro. Aí vem a minha extensão: acrescentar ainda um outro operador sobrecarregado +

    template <typename LLhs, typename LRhs, typename RLhs, typename RRhs>
AddOp<AddOp<LLhs, LRhs>, AddOp<RLhs, RRhs>> operator+(const AddOp<LLhs, LRhs> & leftOperandconst, const AddOp<RLhs, RRhs> & rightOperand)
{
    return  AddOp<AddOp<LLhs, LRhs>, AddOp<RLhs, RRhs>>(leftOperandconst, rightOperand);

}

Agora, o compilador pode lidar com o caso acima corretamente, e nenhuma conversão implícita, Volia!

C ++ 0x é bom e tudo .... mas para aqueles de nós que vivem no presente você tem biblioteca lambda Boost e impulsionar Phoenix. Ambos com a intenção de trazer grandes quantidades de programação funcional para C ++.

Tudo é possível.

Depende exatamente o que você quer dizer:

class X
{
     public: static X& getObjectA()
     {
          static X instanceA;

          return instanceA;
     }
};

Aqui temos o efeito de uma variável global que está preguiçosamente avaliada no ponto de primeiro uso.

Como recém solicitado na pergunta.
E roubar projeto Konrad Rudolph e estendendo-o.

The Lazy objeto:

template<typename O,typename T1,typename T2>
struct Lazy
{
    Lazy(T1 const& l,T2 const& r)
        :lhs(l),rhs(r) {}

    typedef typename O::Result  Result;
    operator Result() const
    {
        O   op;
        return op(lhs,rhs);
    }
    private:
        T1 const&   lhs;
        T2 const&   rhs;
};

Como usá-lo:

namespace M
{
    class Matrix
    {
    };
    struct MatrixAdd
    {
        typedef Matrix  Result;
        Result operator()(Matrix const& lhs,Matrix const& rhs) const
        {
            Result  r;
            return r;
        }
    };
    struct MatrixSub
    {
        typedef Matrix  Result;
        Result operator()(Matrix const& lhs,Matrix const& rhs) const
        {
            Result  r;
            return r;
        }
    };
    template<typename T1,typename T2>
    Lazy<MatrixAdd,T1,T2> operator+(T1 const& lhs,T2 const& rhs)
    {
        return Lazy<MatrixAdd,T1,T2>(lhs,rhs);
    }
    template<typename T1,typename T2>
    Lazy<MatrixSub,T1,T2> operator-(T1 const& lhs,T2 const& rhs)
    {
        return Lazy<MatrixSub,T1,T2>(lhs,rhs);
    }
}

Como isso vai ser feito em C ++ 0x , por expressões lambda.

Em C ++ 11 avaliação preguiçosa semelhante a resposta de hiapay pode ser conseguido usando std :: shared_future. Você ainda tem que cálculos Encapsular em lambdas mas memoization é cuidado:

std::shared_future<int> a = std::async(std::launch::deferred, [](){ return 1+1; });

Aqui está um exemplo completo:

#include <iostream>
#include <future>

#define LAZY(EXPR, ...) std::async(std::launch::deferred, [__VA_ARGS__](){ std::cout << "evaluating "#EXPR << std::endl; return EXPR; })

int main() {
    std::shared_future<int> f1 = LAZY(8);
    std::shared_future<int> f2 = LAZY(2);
    std::shared_future<int> f3 = LAZY(f1.get() * f2.get(), f1, f2);

    std::cout << "f3 = " << f3.get() << std::endl;
    std::cout << "f2 = " << f2.get() << std::endl;
    std::cout << "f1 = " << f1.get() << std::endl;
    return 0;
}

Vamos dar Haskell como nossa inspiração - é ser preguiçoso para o núcleo. Além disso, vamos ter em mente como Linq em C # usos Enumerators em um monádico (urgh - aqui é a palavra - sorry) vias. Última não menos importante, vamos manter em mente, o co-rotinas são supostamente para fornecer aos programadores. Ou seja, a dissociação de passos computacionais (por exemplo produtor de consumo) umas das outras. E vamos tentar pensar em como co-rotinas relacionam com avaliação preguiçosa.

Todas as aparece acima a ser de alguma forma relacionados.

Em seguida, vamos tentar extrair nossa definição pessoal do que "preguiçoso" vem para baixo.

Uma interpretação é: Nós queremos declarar nossa computação de uma forma combináveis, antes executá-lo. Algumas dessas peças que usamos para compor a nossa solução completa pode muito bem recorrer a enormes (às vezes infinitas) fontes de dados, com a nossa computação completo também quer produzir um finito ou infinito resultado.

Vamos começar concreto e em algum código. Precisamos de um exemplo para isso! Aqui, I escolher o "problema" fizzbuzz como exemplo, apenas pela razão de que há algum bom, solução preguiçosa para isso.

Em Haskell, ele se parece com isso:

module FizzBuzz
( fb
)
where
fb n =
    fmap merge fizzBuzzAndNumbers
    where
        fizz = cycle ["","","fizz"]
        buzz = cycle ["","","","","buzz"]
        fizzBuzz = zipWith (++) fizz buzz
        fizzBuzzAndNumbers = zip [1..n] fizzBuzz
        merge (x,s) = if length s == 0 then show x else s

A função cycle Haskell cria uma lista infinita (preguiçoso, é claro!) A partir de uma lista finita simplesmente repetindo os valores na lista finita para sempre. Em um estilo de programação ansioso, escrevendo algo como isso iria tocar sinos de alarme (estouro de memória, loops infinitos!). Mas não é assim em uma língua preguiçoso. O truque é, que as listas preguiçosos não são computados imediatamente. Talvez nunca. Normalmente, apenas o quanto código subseqüente exige.

A terceira linha no bloco where acima cria outro preguiçoso !! lista, por meio de combinando o infinito listas fizz e buzz por meio do único dois elementos receita "concatenar um elemento seqüência a partir de qualquer lista de entrada em uma única cadeia". Novamente, se isso vier a ser imediatamente avaliados, teríamos que esperar para o nosso computador para ficar sem recursos.

Na quarta linha, criamos tuplas dos membros de uma lista preguiçoso [1..n] finito com o nosso infinito preguiçoso lista fizzbuzz. O resultado ainda é preguiçoso.

Mesmo no corpo principal da nossa função fb, não há necessidade de ficar ansioso. Toda a função retorna uma lista com a solução, que é em si -again- preguiçoso. Você poderia muito bem pensar no resultado de fb 50 como um cálculo que você pode (parcialmente) avaliar mais tarde. Ou combinar com outras coisas, levando a uma avaliação ainda maior (preguiçoso).

Assim, a fim de começar com o nosso C ++ versão de "fizzbuzz", precisamos pensar em maneiras como combinar passos parciais da nossa computação em grandes pedaços de cálculos, cada desenho dados de etapas anteriores, conforme necessário.

Você pode ver a história completa em a essência do meu .

Aqui, as idéias básicas por trás do código:

Contracção de C # e Linq, que "inventar" um stateful, genérico tipo Enumerator, que detém
- O valor atual da computação
parcial - O estado de um cálculo parcial (para que possamos produzir valores subseqüentes)
- A função do trabalhador, que produz o próximo estado, o próximo valor e um booleano que indica se há mais dados ou se a enumeração tenha chegado ao fim.

De modo a ser capaz de exemplo Enumerator<T,S> de composição por meio da potência do . (ponto), esta classe também contém funções, emprestados de classes de tipo Haskell, tais como Functor e Applicative.

A função de trabalho para recenseador é sempre da forma: S -> std::tuple<bool,S,T onde S é a variável tipo genérico que representa o estado e T é a variável tipo genérico que representa um valor - o resultado de um computation passo.

Tudo isso já é visível nas primeiras linhas da definição de classe Enumerator.

template <class T, class S>
class Enumerator
{
public:
    typedef typename S State_t;
    typedef typename T Value_t;
    typedef std::function<
        std::tuple<bool, State_t, Value_t>
        (const State_t&
            )
    > Worker_t;

    Enumerator(Worker_t worker, State_t s0)
        : m_worker(worker)
        , m_state(s0)
        , m_value{}
    {
    }
    // ...
};

Assim, tudo o que precisamos para criar uma instância específica recenseador, precisamos criar uma função de trabalho, tem o estado inicial e criar uma instância de Enumerator com esses dois argumentos.

Aqui um exemplo - range(first,last) função cria uma série finita de valores. Isto corresponde a uma lista preguiçoso no mundo Haskell.

template <class T>
Enumerator<T, T> range(const T& first, const T& last)
{
    auto finiteRange =
        [first, last](const T& state)
    {
        T v = state;
        T s1 = (state < last) ? (state + 1) : state;
        bool active = state != s1;
        return std::make_tuple(active, s1, v);
    };
    return Enumerator<T,T>(finiteRange, first);
}

E podemos fazer uso desta função, por exemplo como este: auto r1 = range(size_t{1},10); - Nós criamos a nós mesmos uma lista preguiçoso com 10 elementos

Agora, tudo está faltando para a nossa experiência "wow", é ver como podemos compor recenseadores. Voltando a função cycle Haskells, o que é bem legal. Como ficaria na nossa C ++ mundo? Aqui está:

template <class T, class S>
auto
cycle
( Enumerator<T, S> values
) -> Enumerator<T, S>
{
    auto eternally =
        [values](const S& state) -> std::tuple<bool, S, T>
    {
        auto[active, s1, v] = values.step(state);
        if (active)
        {
            return std::make_tuple(active, s1, v);
        }
        else
        {
            return std::make_tuple(true, values.state(), v);
        }
    };
    return Enumerator<T, S>(eternally, values.state());
}

É preciso um enumerador como entrada e retorna um enumerador. Local (lambda) função eternally simplesmente redefine a enumeração de entrada para o seu valor inicial sempre que se esgote de valores e voilà - temos um infinito, versão da lista que demos como um argumento :: auto foo = cycle(range(size_t{1},3)); sempre repetindo E já podemos descaradamente compor nosso preguiçosos "cálculos".

zip é um bom exemplo, mostrando que também podemos criar um novo enumerador de dois entrevistadores de entrada. Os rendimentos enumerador resultantes como muitos valores como a menor de duas das enumerators entrada (tuplos com dois elementos, um para cada entrada enumerador). Eu tenho implementado zip dentro de si class Enumerator. Aqui está como ele se parece com:

// member function of class Enumerator<S,T> 
template <class T1, class S1>
auto
zip
( Enumerator<T1, S1> other
) -> Enumerator<std::tuple<T, T1>, std::tuple<S, S1> >
{
    auto worker0 = this->m_worker;
    auto worker1 = other.worker();
    auto combine =
        [worker0,worker1](std::tuple<S, S1> state) ->
        std::tuple<bool, std::tuple<S, S1>, std::tuple<T, T1> >
    {
        auto[s0, s1] = state;
        auto[active0, newS0, v0] = worker0(s0);
        auto[active1, newS1, v1] = worker1(s1);
        return std::make_tuple
            ( active0 && active1
            , std::make_tuple(newS0, newS1)
            , std::make_tuple(v0, v1)
            );
    };
    return Enumerator<std::tuple<T, T1>, std::tuple<S, S1> >
        ( combine
        , std::make_tuple(m_state, other.state())
        );
}

Por favor note, como o "combinar" também acaba em combinar o estado de ambas as fontes e os valores de ambas as fontes.

Como este post já está TL; DR; para muitos, aqui o ...

Resumo

Sim, avaliação preguiçosa pode ser implementado em C ++. Aqui, eu fiz isso por meio de empréstimos os nomes das funções de Haskell e o paradigma de C recenseadores # e LINQ. Pode haver semelhanças com jibóias itertools, btw. Eu acho que eles seguido uma abordagem similar.

Meu implementação (veja o link essência acima) é apenas um protótipo - não código de produção, btw. Então, sem quaisquer garantias de meu lado. Ele serve também como demonstração código para obter a idéia geral de diâmetro, apesar de tudo.

E o que esta resposta seja sem a versão final C ++ de fizzbuz, eh? Aqui está:

std::string fizzbuzz(size_t n)
{
    typedef std::vector<std::string> SVec;
    // merge (x,s) = if length s == 0 then show x else s
    auto merge =
        [](const std::tuple<size_t, std::string> & value)
        -> std::string
    {
        auto[x, s] = value;
        if (s.length() > 0) return s; 
        else return std::to_string(x);
    };

    SVec fizzes{ "","","fizz" };
    SVec buzzes{ "","","","","buzz" };

    return
    range(size_t{ 1 }, n)
    .zip
        ( cycle(iterRange(fizzes.cbegin(), fizzes.cend()))
          .zipWith
            ( std::function(concatStrings)
            , cycle(iterRange(buzzes.cbegin(), buzzes.cend()))
            )
        )
    .map<std::string>(merge)
    .statefulFold<std::ostringstream&>
    (
        [](std::ostringstream& oss, const std::string& s) 
        {
            if (0 == oss.tellp())
            {
                oss << s;
            }
            else
            {
                oss << "," << s;
            }
        }
        , std::ostringstream()
    )
    .str();
}

E ... para conduzir o repouso do ponto ainda mais - aqui uma variação de fizzbuzz que retorna uma "lista infinita" para o chamador:

typedef std::vector<std::string> SVec;
static const SVec fizzes{ "","","fizz" };
static const SVec buzzes{ "","","","","buzz" };

auto fizzbuzzInfinite() -> decltype(auto)
{
    // merge (x,s) = if length s == 0 then show x else s
    auto merge =
        [](const std::tuple<size_t, std::string> & value)
        -> std::string
    {
        auto[x, s] = value;
        if (s.length() > 0) return s;
        else return std::to_string(x);
    };

    auto result =
        range(size_t{ 1 })
        .zip
        (cycle(iterRange(fizzes.cbegin(), fizzes.cend()))
            .zipWith
            (std::function(concatStrings)
                , cycle(iterRange(buzzes.cbegin(), buzzes.cend()))
            )
        )
        .map<std::string>(merge)
        ;
    return result;
}

Vale a pena mostrar, desde que você pode aprender com ele como evitar a pergunta que o tipo de retorno exato dessa função é (uma vez que depende da implementação da função por si só, ou seja, como o código combina os recenseadores).

Também demonstra que tivemos que mudar o fizzes vetores e buzzes fora do âmbito da função que eles ainda estão por aí quando, eventualmente, no exterior, o mecanismo preguiçoso produz valores. Se não tivéssemos feito isso, o código iterRange(..) teria armazenado iterators aos vetores que estão muito longe.

Usando uma definição muito simples de avaliação preguiçosa, que é o valor não é avaliado até que seja necessário, eu diria que se pode implementar isso através do uso de um ponteiro e macros (para açúcar de sintaxe).

#include <stdatomic.h>

#define lazy(var_type) lazy_ ## var_type

#define def_lazy_type( var_type ) \
    typedef _Atomic var_type _atomic_ ## var_type; \
    typedef _atomic_ ## var_type * lazy(var_type);  //pointer to atomic type

#define def_lazy_variable(var_type, var_name ) \
    _atomic_ ## var_type _ ## var_name; \
    lazy_ ## var_type var_name = & _ ## var_name;

#define assign_lazy( var_name, val ) atomic_store( & _ ## var_name, val )
#define eval_lazy(var_name) atomic_load( &(*var_name) )

#include <stdio.h>

def_lazy_type(int)

void print_power2 ( lazy(int) i )
{
      printf( "%d\n", eval_lazy(i) * eval_lazy(i) );
}

typedef struct {
    int a;
} simple;

def_lazy_type(simple)

void print_simple ( lazy(simple) s )
{
    simple temp = eval_lazy(s);
    printf("%d\n", temp.a );
}


#define def_lazy_array1( var_type, nElements, var_name ) \
    _atomic_ ## var_type  _ ## var_name [ nElements ]; \
    lazy(var_type) var_name = _ ## var_name; 

int main ( )
{
    //declarations
    def_lazy_variable( int, X )
    def_lazy_variable( simple, Y)
    def_lazy_array1(int,10,Z)
    simple new_simple;

    //first the lazy int
    assign_lazy(X,111);
    print_power2(X);

    //second the lazy struct
    new_simple.a = 555;
    assign_lazy(Y,new_simple);
    print_simple ( Y );

    //third the array of lazy ints
    for(int i=0; i < 10; i++)
    {
        assign_lazy( Z[i], i );
    }

    for(int i=0; i < 10; i++)
    {
        int r = eval_lazy( &Z[i] ); //must pass with &
        printf("%d\n", r );
    }

    return 0;
}

Você vai notar no print_power2 função há um macro chamado eval_lazy que não faz nada mais do que dereference um ponteiro para obter o valor pouco antes de quando é realmente necessário. O tipo preguiçoso é acessado atomicamente, por isso é thread-safe completamente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top