Search code examples
c++11design-patternsbuilderfluent

Type conversion, 'initializing': cannot convert from 'HtmlBuilder *' to 'HtmlElement'


I was trying to follow a tutorial and I am stuck badly at implementing the fluent builder for the unique_ptr as well. Despite knowing that it is a type conversion thing and after inspecting the documentation I wasn't able to find a proper fix.

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <memory>

using namespace std;

class HtmlBuilder;


struct HtmlElement
{
    string name;
    string text;

    vector<HtmlElement> elements;
    const size_t indent_size = 2;


    HtmlElement() {}



    HtmlElement(const string& name, const string& text) : name{ name }, text{ text }
    {

    }

    string str(int indent = 0) const // it is a const because it doesn't change the inner elements of htmlelement
    {
        ostringstream oss;
        string i(indent_size * indent, ' '); // repeating a character as many times as required. 

        oss << i << "<" << name << ">" << endl;


        if (text.size() > 0)
            oss << string(indent_size * (indent + 1), ' ') << text << endl;

        // recursive call
        for (const auto& e : elements)
            oss << e.str(indent + 1);

        oss << i << "</" << name << ">" << endl;
        return oss.str();
    }




    static HtmlBuilder build(string root_name);
    static unique_ptr<HtmlBuilder> create(string root_name);

};



struct HtmlBuilder
{

    HtmlBuilder(string root_name)
    {
        root.name = root_name;
    }




    HtmlElement root; // we can not do anything without root



    HtmlBuilder& add_child(string child_name, string child_text)
    {
        HtmlElement e{ child_name, child_text };


        root.elements.emplace_back(e);


        // it is a reference
        return *this;


    }


    HtmlBuilder* add_child2(string child_name, string child_text)
    {
        HtmlElement e{ child_name, child_text };


        // emplace_back will return a reference to element that was just created in the vector where as push_back does not return anything, so you could preform some chaining if you wanted
        root.elements.emplace_back(e);


        // it is a pointer
        return this;
    }

    string str() const {
        return root.str();
    }


    // let's you convert the builder to htmlelement.
    // automatic conversion.
    // it wil be converted only after the chaining has finished.
    operator HtmlElement() { return root; }





    /*B& operator= (const A& x) { return *this; }*/
    //operator unique_ptr<HtmlElement>() { 


    //  return root; 
    //}

};


// it had to be pasted here after definition of HtmlBuilder
HtmlBuilder HtmlElement::build(string root_name)
{

    // it will get automatically converted to a html element due to 
    // automatic conversion.
    return HtmlBuilder{ root_name };
}


unique_ptr<HtmlBuilder> HtmlElement::create(string root_name) {
    return make_unique<HtmlBuilder>(root_name);
}


// Basically we want to create builder from HtmlElement itself
// and be able to add childs as many as we want and at the end
// it will still return an HtmlElement due to automatic conversion
// in this way we hide builder completely

int main()
{

    HtmlBuilder builder{ "ul" };

    builder.add_child("li", "hello")
        .add_child("li", "world");

    //HtmlElement e = builder;


    // important: automatic conversion occurs only at the end, if the object itself is returned.
    HtmlElement e = HtmlElement::build("ul").add_child("li", "test").add_child("li", "test2");
    HtmlElement ee = HtmlElement::create("ul")->add_child2("li", "test")->add_child2("li", "test2");


    cout << e.str() << endl;





    getchar();
    return 0;
}

The problem is at trying to use use this line:

HtmlElement::create("ul")->add_child2("li", "test")->add_child2("li", "test2");

It throws the error as explained above. It says that cannot convert from 'HtmlBuilder *' to 'HtmlElement'. Tried several solution but I am a beginner in C++ and have not managed to fix it so far.


Solution

  • You've got an operator() to convert from HtmlBuilder to HtmlElement, but not from HtmBuilder* to HtmlElement. That's the difference between your build() line and your create() line.

    So you have to dereference the pointer returned by the create()->add_child2() line.

    Try this

    HtmlElement ee = *(HtmlElement::create("ul")->add_child2("li", "test")->add_child2("li", "test2"));
    

    Running on Wandbox