문제

C++하지 않는가에 대한 기본 지원 게으른 평가(로켈는 않습니다).

나는지 궁금하면 그것을 구현하는 것이 가능하 게으른 평가에서는 C++에서는 합리적인 방식이다.그렇다면,당신은 어떻게 할 것인가요?

편집:내가 좋아하는 콘라드 루돌프의 답입니다.

는지 궁금하네요 가능하면 그것을 구현하기 위해서 보다 일반적인 패션,예를 들어를 사용하여 매개변수화 클래스는 기본적으로 작동합 T 방법 matrix_add 작동을 위한 매트릭스입니다.

모든 작업에서 반환하는 것으 대신 합니다.제를 저장하는 인수와 작동 코드가 내부에 게으른 자체입니다.아무나 볼 수 있는 방법을 향상 이?

도움이 되었습니까?

해결책

합리적인 방식으로 C ++로 게으른 평가를 구현할 수 있는지 궁금합니다. 그렇다면 어떻게 하시겠습니까?

예, 이것은 행렬 계산을 위해 가능하고 자주 수행됩니다. 이를 용이하게하는 주요 메커니즘은 연산자 과부하입니다. 행렬 첨가의 경우를 고려하십시오. 함수의 시그니처는 일반적으로 다음과 같습니다.

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

이제이 기능을 게으르기 위해서는 실제 결과 대신 프록시를 반환하는 것으로 충분합니다.

struct matrix_add;

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

이제해야 할 일은이 대리를 작성하는 것입니다.

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;
};

마법은이 방법에 있습니다 operator matrix() 이는 암시 적 변환 연산자입니다 matrix_add 평범한 matrix. 이렇게하면 여러 작업을 체인 할 수 있습니다 (물론 적절한 오버로드를 제공하여). 평가는 최종 결과가 matrix 사례.

편집하다 나는 더 명확 했어야했다. 그대로, 평가는 게으르게 발생하지만 여전히 동일한 표현식에서 발생하기 때문에 코드는 의미가 없습니다. 특히 다른 추가 기능은 matrix_add 체인 첨가를 허용하도록 구조가 변경됩니다. C ++ 0X는 변수 템플릿 (예 : 가변 길이의 템플릿 목록)을 허용하여이를 크게 용이하게합니다.

그러나이 코드가 실제로 실제적이고 직접적인 이점을 가진 매우 간단한 경우는 다음과 같습니다.

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

여기에서는 가정합니다 A 그리고 B 2 차원 매트릭스이며 탈환은 Fortran 표기법으로 수행됩니다. 즉, 위의 계산 하나 행렬 합계에서 요소. 물론 전체 행렬을 추가하는 것은 낭비입니다. matrix_add 구조에 :

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);
    }
};

다른 예가 많습니다. 나는 얼마 전에 관련된 것을 구현했다는 것을 기억했습니다. 기본적으로 고정 된 사전 정의 된 인터페이스를 준수 해야하는 문자열 클래스를 구현해야했습니다. 그러나 내 특정 문자열 클래스는 실제로 메모리에 저장되지 않은 거대한 문자열을 다루었습니다. 일반적으로 사용자는 함수를 사용하여 원래 문자열에서 작은 하위 문자열에 액세스합니다. infix. 내 문자열 유형에 대해이 함수를 과부하시켜 원하는 시작 및 엔드 위치와 함께 문자열에 대한 참조를 보유한 프록시를 반환했습니다. 이 서브 스트링이 실제로 사용 된 경우에만 문자열 의이 부분을 검색하기 위해 C API를 쿼리했습니다.

다른 팁

boost.lambda는 매우 훌륭하지만 부스트 .proto ~이다 바로 그거죠 당신이 찾고있는 것. 이미 과부하가 있습니다 모두 기본적으로 일반적인 기능을 수행하는 C ++ 연산자 proto::eval() 호출되지만 변경할 수 있습니다.

Konrad가 이미 설명한 내용은 더 게으른 실행 된 운영자의 중첩 된 호출을 지원하기 위해 더 자세히 설명 할 수 있습니다. Konrad의 예에서, 그는 정확히 두 개의 오페라에 대해 정확히 두 개의 인수를 저장할 수있는 표현 객체를 가지고 있습니다. 문제는 그것이 실행된다는 것입니다 하나 게으른 평가의 개념을 간단한 용어로 설명하지만 실질적으로 성능을 향상 시키지는 않지만 Subexpression Lazely. 다른 예제는 또한 어떻게 적용 할 수 있는지 잘 보여줍니다. operator() 해당 표현 객체를 사용하여 일부 요소 만 추가합니다. 그러나 임의의 복잡한 표현을 평가하려면 가게 그것의 구조도. 우리는 템플릿을 돌아 다닐 수 없습니다. 그리고 그 이름은입니다 expression templates. 아이디어는 하나의 템플릿 식 객체가 작업이 노드이고 피연산자가 어린이 노드라는 트리와 같이 임의의 하위 표현의 구조를 재귀 적으로 저장할 수 있다는 것입니다. a 매우 오늘 방금 찾은 좋은 설명 (아래 코드를 쓴 후 며칠) 여기.

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; }
};

간단한 포인트 유형에 대해 연산자+의 다음 정의에서 볼 수 있듯이 모든 추가 작업 (중첩 된 작업)을 저장합니다.

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);
}

지금, 당신이 있다면

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

이제 오퍼레이터를 과부하로로드하고 포인트 유형에 적합한 생성자를 추가하고 추가 기능을 수락해야합니다. 정의를 다음과 같이 변경합니다.

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; }
};

그리고 멤버 기능으로 적절한 get_x 및 get_y를 추가로 추가하십시오.

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

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

유형 포인트의 임시를 어떻게 만들지 않았는지 주목하십시오. 많은 필드가있는 큰 매트릭스 일 수 있습니다. 그러나 결과가 필요할 때 우리는 그것을 계산합니다. 게으름.

Konrad의 게시물에 추가 할 것이 없지만 볼 수 있습니다. 고유 게으른 평가의 예는 실제 앱에서 올바르게 수행되었습니다. 꽤 경외심을 느낍니다.

사용하는 템플릿 클래스를 구현하려고 생각하고 있습니다. std::function. 클래스는 다음과 같이 보인다.

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;
};

예를 들어 사용법 :

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();
}

위의 코드는 콘솔에서 다음 메시지를 생성해야합니다.

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

생성자 인쇄 Noisy(10) 변수에 액세스 할 때까지 호출되지 않습니다.

그러나이 수업은 완벽하지 않습니다. 첫 번째는 기본 생성자입니다 Value 회원 초기화에 따라 호출해야합니다 (인쇄 Noisy(0) 이 경우). 우리는 포인터를 사용할 수 있습니다 _value 대신, 그러나 그것이 성능에 영향을 줄지 확실하지 않습니다.

요하네스'답 작동합니다.하지만 그것이 더 많은 괄호로 작동하지 않는다.여기에는 예입니다.

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

기 때문에 세 가지로 오버로드+운영자를 다루지 않는 경우

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

도록 컴파일러로 변환하거나(p1+p2)또는(p3+p4)포인트는 게으른하지 않 충분하다.을 때 컴파일러 결정을 변환,그것은 뿌려줍니다.기 때문에 아무도 더 이상 다른.여기에 온 나의 확장자:추가 또 다른 운영자 과부+

    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);

}

이제,컴파일러가 처리할 수 있는 위의 경우 올바르게는 암시적 변환 volia!

C ++ 0X는 훌륭합니다 .... 그러나 현재에 사는 사람들에게는 Lambda Library를 부스트하고 Phoenix를 부스트합니다. 모두 C ++에 대량의 기능 프로그래밍을 가져 오려는 의도로.

무엇이든 가능합니다.

그것은 당신이 의미하는 바에 달려 있습니다.

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

          return instanceA;
     }
};

여기서 우리는 첫 번째 사용 시점에서 게으르게 평가되는 글로벌 변수의 영향을 미칩니다.

질문에 새로 요청한대로.
Konrad Rudolph 디자인을 훔치고 확장합니다.

게으른 대상 :

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;
};

사용 방법:

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);
    }
}

그것이 끝날 것입니다 C ++ 0x, 람다 표현에 의해.

C ++ 11에서 HiaPay의 답변과 유사한 게으른 평가는 std :: shared_future를 사용하여 달성 할 수 있습니다. 여전히 Lambdas에서 계산을 캡슐화해야하지만 회고록은 다음을 처리합니다.

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

다음은 전체 예입니다.

#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;
}

Haskell을 우리의 영감으로 삼아 봅시다. 그것은 핵심에 게으르다. 또한 C#의 LINQ가 모나디아에서 열거자를 어떻게 사용하는지 명심해 봅시다 (urgh- 여기 단어가 있습니다 - 죄송합니다). 마지막으로, 코 루틴이 프로그래머에게 제공해야 할 사항을 명심하십시오. 즉, 계산 단계 (예 : 생산자 소비자)의 분리. 그리고 코 루틴이 게으른 평가와 어떤 관련이 있는지 생각해 봅시다.

위의 모든 것은 어떻게 든 관련이있는 것으로 보입니다.

다음으로, "게으른"에 대한 개인적인 정의를 추출하려고 노력합시다.

한 가지 해석은 다음과 같습니다. 우리는 우리의 계산을 복합 가능한 방식으로 진술하고 싶습니다. ~ 전에 실행. 우리가 완전한 솔루션을 작성하는 데 사용하는 일부 부품 중 일부는 거대한 (때로는 무한한) 데이터 소스를 매우 잘 활용할 수 있으며, 전체 계산은 유한 한 결과 또는 무한 결과를 생성합니다.

콘크리트와 일부 코드로 들어갑니다. 우리는 그것에 대한 예가 필요합니다! 여기에서 나는 fizzbuzz "문제"를 예로 선택합니다.

Haskell에서는 다음과 같습니다.

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

Haskell 기능 cycle 유한 한 목록의 값을 영원히 반복하여 유한 한 목록에서 무한 목록 (게으른!)을 만듭니다. 열렬한 프로그래밍 스타일에서, 그런 것을 쓰면 알람 벨 (메모리 오버플로, 끝없는 루프)이 울릴 것입니다. 그러나 게으른 언어로는 그렇지 않습니다. 속임수는 게으른 목록이 바로 계산되지 않는다는 것입니다. 어쩌면 절대. 일반적으로 후속 코드가 요구하는만큼만.

세 번째 줄 where 위의 블록은 또 다른 게으른 것을 만듭니다 !! 무한한 목록을 결합하여 목록 fizz 그리고 buzz 단일 두 요소 레시피를 통해 "입력 목록에서 하나의 문자열로 문자열 요소를 연결하십시오". 다시 말하지만, 이것이 즉시 평가되면 컴퓨터가 자원이 부족할 때까지 기다려야합니다.

4 라인에서, 우리는 유한 게으른 목록의 튜플을 만듭니다. [1..n] 우리의 무한한 게으른 목록으로 fizzbuzz. 결과는 여전히 게으르다.

우리의 본체에서도 fb 기능, 열망 할 필요가 없습니다. 전체 함수는 솔루션으로 목록을 반환합니다. 당신은 결과를 생각할 수 있습니다 fb 50 나중에 (부분적으로) 평가할 수있는 계산으로. 또는 다른 것들과 결합하여 더 큰 (게으른) 평가로 이어집니다.

따라서 "Fizzbuzz"의 C ++ 버전을 시작하려면 계산의 부분 단계를 더 큰 계산으로 결합하는 방법을 생각해야합니다.

전체 이야기를 볼 수 있습니다 내 요점.

여기서 코드의 기본 아이디어 :

C# 및 LINQ에서 차입, 우리는 상태가 있고 일반적인 유형을 "발명"합니다. Enumerator, 보유합니다
- 부분 계산의 현재 값
- 부분 계산 상태 (따라서 후속 값을 생성 할 수 있습니다)
- 다음 상태, 다음 값 및 더 많은 데이터가 있는지 또는 열거가 끝나는 경우에 나타나는 BOOL을 생성하는 작업자 기능.

작곡 할 수 있도록 Enumerator<T,S> 인스턴스의 힘에 의한 인스턴스 . (DOT),이 클래스는 또한 Haskell 유형 클래스에서 빌린 기능도 포함되어 있습니다. Functor 그리고 Applicative.

열거 자의 작업자 기능은 항상 형식입니다. S -> std::tuple<bool,S,T 어디 S 상태를 나타내는 일반 유형 변수입니다 T 계산 단계의 결과를 나타내는 일반 유형 변수입니다.

이 모든 것은 이미 첫 번째 줄에서 볼 수 있습니다. 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{}
    {
    }
    // ...
};

따라서 특정 열거 자 인스턴스를 작성하는 데 필요한 모든 것이 있습니다. 작업자 기능을 작성하고 초기 상태를 갖고 인스턴스를 작성해야합니다. Enumerator 그 두 가지 주장으로.

여기서 예 - 기능 range(first,last) 유한 한 값 범위를 만듭니다. 이것은 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);
}

예를 들어 다음과 같이이 기능을 사용할 수 있습니다. auto r1 = range(size_t{1},10); - 우리는 10 개의 요소가있는 게으른 목록을 만들었습니다!

이제 우리의 "와우"경험을 위해 모든 것이 누락되었으며, 우리가 열거자를 작성하는 방법을 보는 것입니다. Haskells로 돌아옵니다 cycle 기능, 그것은 멋지다. C ++ 세계에서 어떻게 보일까요? 여기있어:

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());
}

열거자를 입력으로 가져 와서 열거자를 반환합니다. 로컬 (Lambda) 기능 eternally 입력 열거가 값이 부족할 때마다 입력 열거를 시작 값으로 재설정합니다. auto foo = cycle(range(size_t{1},3)); 그리고 우리는 이미 게으른 "계산"을 부끄럽게 구성 할 수 있습니다.

zip 두 입력 열거 자로부터 새 열거자를 만들 수 있음을 보여주는 좋은 예입니다. 결과 열거자는 입력 열거 자 중 하나보다 작은 값만큼 많은 값을 산출합니다 (2 요소가있는 튜플, 각 입력 열거 자에 대해 하나). 나는 구현했다 zip 내부에 class Enumerator 그 자체. 다음은 다음과 같습니다.

// 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())
        );
}

"결합"이 두 소스의 상태와 두 소스의 값을 결합하는 방법에 대해서도 참조하십시오.

이 게시물은 이미 tl; dr; 많은 사람들에게 여기, 여기 ...

요약

예, 게으른 평가는 C ++에서 구현 될 수 있습니다. 여기서 나는 Haskell의 기능 이름과 C# Enumerators 및 LINQ의 패러다임을 빌려서 그렇게했습니다. Pythons Itertools, BTW와 유사 할 수 있습니다. 나는 그들이 비슷한 접근법을 따랐다 고 생각합니다.

내 구현 (위의 GIST 링크 참조)은 프로덕션 코드가 아닌 프로토 타입입니다. 그래서 내 편에서는 보증이 없습니다. 그러나 일반적인 아이디어를 얻기 위해 데모 코드 역할을합니다.

그리고이 대답은 Fizzbuz의 최종 C ++ 버전이 없을까요? 여기있어:

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();
}

그리고 ... 포인트를 더 멀리 운전하기 위해 - 여기서 "무한 목록"을 발신자에게 반환하는 fizzbuzz의 변형 :

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;
}

해당 기능의 정확한 반환 유형이 무엇인지에 대한 질문을 피하는 방법을 배울 수 있기 때문에 (함수의 구현, 즉 코드가 열거자를 결합하는 방법)를 배울 수 있기 때문에 보여줄 가치가 있습니다.

또한 벡터를 움직여야했음을 보여줍니다. fizzes 그리고 buzzes 함수의 범위를 벗어나서 결국 외부에있을 때 여전히 주변에있을 때 게으른 메커니즘은 값을 생성합니다. 우리가 그렇게하지 않았다면 iterRange(..) 코드는 오랫동안 사라진 벡터에 반복자를 저장했을 것입니다.

게으른 평가에 대한 매우 간단한 정의를 사용하여 값이 필요할 때까지 평가되지 않으면 포인터와 매크로 (구문 설탕)를 사용하여이를 구현할 수 있다고 말합니다.

#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;
}

기능에서 알 수 있습니다 print_power2 매크로 호출이 있습니다 eval_lazy 실제로 필요한 경우 바로 직전에 가치를 얻는 포인터를 불신하는 것만으로도 적용되지 않습니다. 게으른 유형은 원자 적으로 액세스되므로 완전히 실로 안전합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top