vendredi 18 septembre 2020

How to safely write a store that when updated never calls the copy constructor of the stored value?

I have the problem in some generic code that I require an accumulator or more precisely a mutable variable to hold a temporary in a loop. The code is part of a Reduce implementation. I got too clever and found my self folding over sequences of objects where each object may contain a reference. A fold in a non recursive implementation requires an accumulator to hold the current value. In pseudocode ( as the question is not about how to write a fold )

optional<T> acc = none;
while(e.HasValue()){
    acc = folder(acc, e.Current());
    e.MoveNext();
}

However if T is a value type ( not a reference ) but contains references. For example std::pair<int&,int&> then the line

acc = folder(acc,e.Current());

will update acc but will do this through the copy constructor. Thus any reference members of T will assign through the references rather than rebinding. The below test case shows what happens.

TEST(LINQ, Accumumator){
    int a = 10;
    int b = 20;
    int c = 30;

    using TR = std::pair<int&,int&>;

    TR acc(TR(a,b));

    EXPECT_EQ(acc.first,10);
    EXPECT_EQ(acc.second,20);
    EXPECT_EQ(a,10);
    EXPECT_EQ(b,20);
    EXPECT_EQ(c,30);

    acc = TR(b,c);

    EXPECT_EQ(acc.first,20);
    EXPECT_EQ(acc.second,30);

    // This will fail as 'a' now has value 20
    EXPECT_EQ(a,10);
    // This will fail as 'b' now has value 30
    EXPECT_EQ(b,20);
    EXPECT_EQ(c,30);
}

The above is as expected. However it is not the behaviour I want in my accumulator. I want the following. I need a class RebindWrapper that will block any usage of the copy constructor.


TEST(LINQ, RebindAccumumator){
    int a = 10;
    int b = 20;
    int c = 30;

    using TR = std::pair<int&,int&>;

    RebindWrapper<TR> acc(TR(a,b));

    EXPECT_EQ(acc->first,10);
    EXPECT_EQ(acc->second,20);
    EXPECT_EQ(a,10);
    EXPECT_EQ(b,20);
    EXPECT_EQ(c,30);

    acc = TR(b,c);

    EXPECT_EQ(acc->first,20);
    EXPECT_EQ(acc->second,30);
    EXPECT_EQ(a,10);
    EXPECT_EQ(b,20);
    EXPECT_EQ(c,30);
}

My first attempt to do this is as follows

/// @brief Magic class that can store a value or reference but when updated the copy constructor
/// of the stored value is not called, rather just the constructor. The use case is that reference
/// members of 'T' are rebound rather than assigned through
/// @tparam T 
template <typename T> struct RebindWrapper {

    T m_value;

    RebindWrapper(T v):m_value(v){}
    T operator*(){return m_value;}
    T * operator->(){return &m_value;}

    RebindWrapper<T> & operator = (RebindWrapper<T> const & other){

        m_value.~T();
        ::new(&m_value) T(other.m_value);
    }
    RebindWrapper<T> & operator = (T other){
        m_value.~T();
        ::new(&m_value) T(other);
    }
};

The above implementation makes my test case pass. However it looks super scary and I can't think of what might go wrong. My question is, is the above implementation of RebindWrapper robust and/or are there any gotchas I should worry about.

Aucun commentaire:

Enregistrer un commentaire