I'm trying to make some nice syntax for assignment of array slices using operator overloading. Due to some peculiarities with const-correctness of references, different classes are needed for lvalue and rvalue slices. However, I cannot get the compiler to understand which version to use in a given context.
The code below shows a simplified version of the original code, and while it looks a bit contrived, it captures the issue fairly well. In the real code, i
is a whole set of indices, and B
/ConstB
represents slices of huge arrays that cannot be copied around, motivating the reference x
.
The problem occurs when I want to do the assignment in foo
. I want all slices on the right to yield a ConstB
object, but since a1
happens to be non-const in this case, the wrong one is used. I can enforce it by introducing a const_cast
, but I would prefer some way that does not destroy the user code (i.e. the thing inside foo
). Is there a way to enforce that a given overload is called for lvalues or rvalues, respectively?
The code:
struct B;
struct ConstB;
struct A
{
double data[10];
A() {}
A& operator=(const ConstB &b);
ConstB operator()(int i) const;
B operator()(int i);
};
struct B
{
double &x;
B(double *d, int i);
B& operator=(const A &a);
};
struct ConstB
{
const double x;
ConstB(const double *d, int i);
};
A& A::operator=(const ConstB &b) {
data[0] = b.x;
return *this;
}
ConstB A::operator()(int i) const {
return ConstB(data,i);
}
B A::operator()(int i) {
return B(data,i);
}
B::B(double *d, int i)
: x(d[i]) {}
B& B::operator=(const A &a) {
x=a.data[0];
return *this;
}
ConstB::ConstB(const double *d, int i)
: x(d[i]) {}
void foo(A &a1, A &a2, A&a3)
{
a1(2) = a2;
a3 = a1(3);
// a3 = const_cast<const A&>(a1)(3);
}
int main(int argc, char *argv[])
{
A a1;
A a2;
A a3;
foo(a1,a2,a3);
return 0;
}
Resulting error message:
test.cc: In function ‘void foo(A&, A&, A&)’:
test.cc:67:6: error: no match for ‘operator=’ (operand types are ‘A’ and ‘B’)
a3 = a1(3);
^
test.cc:36:4: note: candidate: A& A::operator=(const ConstB&)
A& A::operator=(const ConstB &b) {
^
test.cc:36:4: note: no known conversion for argument 1 from ‘B’ to ‘const ConstB&’
test.cc:5:8: note: candidate: A& A::operator=(const A&)
struct A
^
test.cc:5:8: note: no known conversion for argument 1 from ‘B’ to ‘const A&’
test.cc:5:8: note: candidate: A& A::operator=(A&&)
test.cc:5:8: note: no known conversion for argument 1 from ‘B’ to ‘A&&’