Ivor Denham-Dyson
Ivor Denham-Dyson

Reputation: 685

How to use class templates to remove boiler plate code?

In the below example there is a base class and two derived classes. The derived classes are no different from each other but I included it simply to illustrate that there can be multiple child classes. The code creates a derived class (implicitly up-cast, think the factory design pattern) which is cloned polymorphically to a smart pointer (exactly as was done here Clone pattern for std::shared_ptr in C++) and then an attribute of the class is changed to show the cloning has worked as intended. This runs great and returns the desired output "Source:4 Clone:1". I wanted to know if its possible to remover the boiler plate code though?

#include "pch.h"
#include <iostream>

class Node
{
public:
    Node(int);
    int value;
    virtual ~Node() = default;
    virtual void set_value(int) = 0;
    std::shared_ptr<Node> Clone() const;

private:
    virtual Node * CloneImplementation() const = 0;
};

Node::Node(int VALUE) {
    value = VALUE;
}

std::shared_ptr<Node> Node::Clone() const {
    return std::shared_ptr<Node>(CloneImplementation());
};

class DerivedNode1 : public Node
{
public:
    DerivedNode1(int);
    void set_value(int);
    std::shared_ptr<DerivedNode1> Clone() const;

private:
    DerivedNode1 * CloneImplementation() const override;
};

DerivedNode1::DerivedNode1(int VALUE) : Node(VALUE) {
}

std::shared_ptr<DerivedNode1>  DerivedNode1::Clone() const {
    return std::shared_ptr<DerivedNode1>(CloneImplementation());
};

DerivedNode1 * DerivedNode1::CloneImplementation() const {
    return new DerivedNode1(*this);
};

void DerivedNode1::set_value(int NEW_VALUE) {
    value = NEW_VALUE;
}

class DerivedNode2 : public Node
{
public:
    DerivedNode2(int);
    void set_value(int);
    std::shared_ptr<DerivedNode2> Clone() const;

private:
    DerivedNode2 * CloneImplementation() const override;
};

DerivedNode2::DerivedNode2(int VALUE) : Node(VALUE) {
}

std::shared_ptr<DerivedNode2>  DerivedNode2::Clone() const {
    return std::shared_ptr<DerivedNode2>(CloneImplementation());
};

DerivedNode2 * DerivedNode2::CloneImplementation() const {
    return new DerivedNode2(*this);
};

void DerivedNode2::set_value(int NEW_VALUE) {
    value = NEW_VALUE;
}

std::shared_ptr<Node> ImplicitUpCast(int type) {
    if (type == 1) {
        return std::make_shared<DerivedNode1>(1);
    } else {
        return std::make_shared<DerivedNode2>(1);
    }
}

int main() {
    std::shared_ptr<Node> sourceNode1 = ImplicitUpCast(1);
    std::shared_ptr<Node> cloneNode1 = sourceNode1->Clone();
    sourceNode1->set_value(4);

    std::cout << "Source: " << sourceNode1->value << " Clone: " << cloneNode1->value;
}

Making and declaring the below methods in each child class seems tedious and I wanted a workaround.

std::shared_ptr<DerivedNode1>  DerivedNode1::Clone() const {
    return std::shared_ptr<DerivedNode1>(CloneImplementation());
};

DerivedNode1 * DerivedNode1::CloneImplementation() const {
    return new DerivedNode1(*this);
};

My approach was to use an 'intermediary' class template that DerivedNode1 and DerivedNode2 inherited from and contained both these methods. Here is my attempt.

#include "pch.h"
#include <iostream>

class Node
{
public:
    Node(int);
    int value;
    virtual ~Node() = default;
    virtual void set_value(int) = 0;
    std::shared_ptr<Node> Clone() const;

private:
    virtual Node * CloneImplementation() const = 0;
};

Node::Node(int VALUE) {
    value = VALUE;
}

std::shared_ptr<Node> Node::Clone() const {
    return std::shared_ptr<Node>(CloneImplementation());
};


template<class source>
class NodeExtended : public Node
{
public:
    NodeExtended(int);
    std::shared_ptr<source> Clone() const;
private:
    source* CloneImplementation() const override;
};

template<class source>
NodeExtended<source>::NodeExtended(int VALUE) : Node(VALUE) {
}

template<class source>
std::shared_ptr<source> NodeExtended<source>::Clone() const {
    return std::shared_ptr<source>(CloneImplementation());
};

template<class source>
source * NodeExtended<source>::CloneImplementation() const {
    return new source(*this);
};

class DerivedNode1 : public NodeExtended<DerivedNode1>
{
public:
    DerivedNode1(int);
    void set_value(int);

};

DerivedNode1::DerivedNode1(int VALUE) : NodeExtended(VALUE) {
}

void DerivedNode1::set_value(int NEW_VALUE) {
    value = NEW_VALUE;
}

class DerivedNode2 : public NodeExtended<DerivedNode2>
{
public:
    DerivedNode2(int);
    void set_value(int);
};

DerivedNode2::DerivedNode2(int VALUE) : NodeExtended(VALUE) {
}

void DerivedNode2::set_value(int NEW_VALUE) {
    value = NEW_VALUE;
}

std::shared_ptr<Node> ImplicitUpCast(int type) {
    if (type == 1) {
        return std::make_shared<DerivedNode1>(1);
    } else {
        return std::make_shared<DerivedNode2>(1);
    }
}

int main() {
    //std::shared_ptr<Node> sourceNode1 = ImplicitUpCast(1);
    //std::shared_ptr<Node> cloneNode1 = sourceNode1->Clone();
    //sourceNode1->set_value(4);

    //std::cout << "Source: " << sourceNode1->value << " Clone: " << cloneNode1->value;
}

I receive the error 'NodeExtended::CloneImplementation': unable to match function definition to an existing declaration' on the following line

source * NodeExtended<source>::CloneImplementation() const {
    return new source(*this);
};

I am assuming this is because Node and source are not viewed as parent and child pointers and the polymorphism breaks down because of the return type, similarly to how a smart pointer might throw an error in the situation.

I am unsure how to continue from here. I think I don't understand class templates well enough but no tutorials really capture my use case.

Any and all help would be appreciated.

Edit:

The approved answer finds the main problem but I also had to change some of the source return types to node which still gives the desired behavior. Here is the complete solution.

#include "pch.h"
#include <iostream>

class Node
{
public:
    Node(int);
    int value;
    virtual ~Node() = default;
    virtual void set_value(int) = 0;
    std::shared_ptr<Node> Clone() const;

private:
    virtual Node * CloneImplementation() const = 0;
};

Node::Node(int VALUE) {
    value = VALUE;
}

std::shared_ptr<Node> Node::Clone() const {
    return std::shared_ptr<Node>(CloneImplementation());
};


template<class source>
class NodeExtended : public Node
{
public:
    NodeExtended(int);
    std::shared_ptr<source> Clone() const;
private:
    Node * CloneImplementation() const override;
};

template<class source>
NodeExtended<source>::NodeExtended(int VALUE) : Node(VALUE) {
}

template<class source>
std::shared_ptr<source> NodeExtended<source>::Clone() const {
    return std::shared_ptr<source>(static_cast<source*>(CloneImplementation()));
};

template<class source>
Node * NodeExtended<source>::CloneImplementation() const {
    return new source(static_cast<const source&>(*this));
};

class DerivedNode1 : public NodeExtended<DerivedNode1>
{
public:
    DerivedNode1(int);
    void set_value(int);

};

DerivedNode1::DerivedNode1(int VALUE) : NodeExtended(VALUE) {
}

void DerivedNode1::set_value(int NEW_VALUE) {
    value = NEW_VALUE;
}

class DerivedNode2 : public NodeExtended<DerivedNode1>
{
public:
    DerivedNode2(int);
    void set_value(int);
};

DerivedNode2::DerivedNode2(int VALUE) : NodeExtended(VALUE) {
}

void DerivedNode2::set_value(int NEW_VALUE) {
    value = NEW_VALUE;
}

std::shared_ptr<Node> ImplicitUpCast(int type) {
    if (type == 1) {
        return std::make_shared<DerivedNode1>(1);
    } else {
        return std::make_shared<DerivedNode2>(1);
    }
}

int main() {
    std::shared_ptr<Node> sourceNode1 = ImplicitUpCast(1);
    std::shared_ptr<Node> cloneNode1 = sourceNode1->Clone();
    sourceNode1->set_value(4);

    std::cout << "Source: " << sourceNode1->value << " Clone: " << cloneNode1->value;
}

Upvotes: 0

Views: 326

Answers (2)

Jarod42
Jarod42

Reputation: 217275

Issue with CRTP is that class is not yet a complete type. which makes Clonable more difficult and requires some casts.

template<class source>
class NodeExtended : public Node
{
public:
    using Node::Node; // inherit constructors

    std::shared_ptr<source> Clone() const
    {
        return std::shared_ptr<source>(static_cast<source*>(CloneImplementation()));
    }
private:
    // Cannot use covariant return type as source is not complete.
    Node* CloneImplementation() const override
    {
        return new source(static_cast<const source&>(*this));
    }
};

Upvotes: 1

JEagle
JEagle

Reputation: 46

std::shared_ptr<DerivedNode1> DerivedNode1::Clone() const and std::shared_ptr<DerivedNode2> DerivedNode2::Clone() const not called in your sample. All works fine without these functions. In template example in function

template<class source>
source * NodeExtended<source>::CloneImplementation() const {
  return new source(*this);
};

you try to call source(NodeExtended &) constructor. You need something like

return new source(*static_cast<const source*>(this));

Then all should work.

Upvotes: 0

Related Questions