What I need
I have a data structure Tree with two levels of proxies one for Branch and a more detailed Leaf.
Here is a MCVE:
#include <vector>
#include <iostream>
class Tree {
public:
class Leaf;
class Branch {
public:
Branch(Tree* tree, int branch_id)
: tree_(tree), branch_id_(branch_id) {};
Leaf leaf(int leaf_id) {
return Leaf{ tree_, branch_id_, leaf_id };
}
float thickness() const {
return tree_->branch_thickness_[branch_id_];
}
void set_thickness(float thickness) {
tree_->branch_thickness_[branch_id_] = thickness;
}
private:
Tree* tree_;
int branch_id_;
};
class Leaf {
public:
Leaf(Tree* tree, int branch_id, int leaf_id)
: tree_(tree), branch_id_(branch_id), leaf_id_(leaf_id) {};
Branch branch() {
return Branch{ tree_, branch_id_ };
}
float color() const {
return tree_->leaf_color_[branch_id_][leaf_id_];
}
void set_color(float color) {
tree_->leaf_color_[branch_id_][leaf_id_] = color;
}
private:
Tree* tree_;
int branch_id_;
int leaf_id_;
};
Branch branch(int branch_id) {
return Branch{ this, branch_id };
}
Branch branch(int branch_id) const {
// Compile error:
// candidate constructor not viable: 1st argument ('const Tree *') would lose const qualifier
return Branch{ this, branch_id };
}
private:
std::vector<float> branch_thickness_{ 0.5 };
std::vector<std::vector<float>> leaf_color_{ {0.2, 0.4} };
};
void demo() {
Tree tree;
Tree::Branch branch = tree.branch(0);
Tree::Leaf leaf = branch.leaf(1);
std::cout << "Branch Thickness " << branch.thickness() << '\n';
std::cout << "Leaf Color " << leaf.color() << '\n';
branch.set_thickness(0.25);
std::cout << "Branch Thickness " << leaf.branch().thickness() << '\n';
}
void demo_const() {
const Tree tree;
Tree::Branch branch = tree.branch(0);
Tree::Leaf leaf = branch.leaf(1);
std::cout << "Branch Thickness " << branch.thickness() << '\n';
std::cout << "Leaf Color " << leaf.color() << '\n';
std::cout << "Branch Thickness " << leaf.branch().thickness() << '\n';
}
int main() {
demo();
demo_const();
return 0;
}
Compiling gives me the following error:
error : no matching constructor for initialization of 'Tree::Branch'
return Branch{ this, branch_id };
^ ~~~~~~~~~~~~~~~~~~~
note: candidate constructor not viable: 1st argument ('const Tree *') would lose const qualifier
Branch(Tree* tree, int branch_id)
^
note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided
class Branch {
^
note: candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 2 were provided
How do I get this to compile and both demo and demo_const to work.
What I tried so far
I read up on the topic and the pattern I found to template the reference to the tree und instanciate it for Tree and const Tree, as shown here and here.
template <TreeType>
class BranchTemplate {
public:
BranchTemplate(TreeType *tree, int branch_id)
tree_(tree),
branch_id_(branch_id)
{
}
private:
TreeType *tree_;
int branch_id_;
};
using Branch = BranchTemplate<Tree>;
using ConstBranch= BranchTemplate<const Tree>;
class Tree {
Branch branch(int branch_id)
{
return Branch(this, branch_id);
}
ConstBranch branch(int branch_id) const
{
return ConstBranch(this, branch_id);
}
}
While this works for Branch I can't do the same for Leaf as we cannot return a Leaf based on the constness of the branch, but this needs to depend on the constness of the template parameter. Adding more template parameters eventually lead to recursion issues.
Plan B
My plan B is to create two more independent classes ConstBranch and ConstLeaf. This however would duplicate the implementation, and might introduce subtle bugs when they are not consistent with each other.
Other ideas
I read about enable_if and think it might help here. However I couldn't find an example that made it clear how that would work.
Question
How are other people solving this issue?
Since your edit shows you want the
tree_members to match the const-ness of theLeafandBranchobjects, the template approach is now appropriate.Demo
Trying
set_thicknesson a branch of aconst Treewill result in a linker error since it's just not defined. If you want a better error message, you can write a static_assert instead:I think having a
bool Constas template parameter instead of atypename TreeTypeis easier, though you could stick with theTreeType. You would just need to defineLeafTemplate::BranchTypeandBranchTemplate::LeafTypeappropriately. No need for recursion.Demo