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