0

This is a continuation of this previous question removing code duplication and user 1201programalarm suggested to use CRTP to resolve my issue. I'm trying to take their advice and when I try to apply it to my Bus class Visual Studio is generating compiler errors...

When I try to do this:

template<size_t BusSize>
class Bus : public Component, ComponentID<Bus> {

I get this error:

>------ Build started: Project: Simulator, Configuration: Debug x64 ------
1>main.cpp
1>c:\***\bus.h(6): error C3203: 'Bus': unspecialized class template can't be used as a template argument for template parameter 'T', expected a real type
1>c:\***\bus.h(49): note: see reference to class template instantiation 'Bus<BusSize>' being compiled
1>Done building project "Simulator.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

And when I try to change it to this:

template<size_t BusSize>
class Bus : public Component, ComponentID<Bus<BusSize>> {

It then gives me these errors:

>------ Build started: Project: Simulator, Configuration: Debug x64 ------
1>main.cpp
1>c:\***\bus.h(11): error C2512: 'ComponentID<Bus<4>>': no appropriate default constructor available
1>c:\***\bus.h(6): note: see declaration of 'ComponentID<Bus<4>>'
1>c:\***\bus.h(11): note: while compiling class template member function 'Bus<4>::Bus(const std::string &)'
1>c:\***\main.cpp(10): note: see reference to function template instantiation 'Bus<4>::Bus(const std::string &)' being compiled
1>c:\***\main.cpp(10): note: see reference to class template instantiation 'Bus<4>' being compiled
1>c:\***\bus.h(11): error C2614: 'Bus<4>': illegal member initialization: 'ComponentID' is not a base or member
1>Done building project "Simulator.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

I'm not sure what else to try to resolve these compiler errors...

Here is my source code...

main.cpp

#include <exception>

#include "Bus.h"

int main() {
    try {
        Wire w1, w2, w3, w4;
        std::cout << w1.id() << " " << w2.id() << " " << w3.id() << " "<< w4.id() << "\n\n";

        Bus<4> b1;

        b1.connect(&w1, 0);
        b1.connect(&w2, 1);
        b1.connect(&w3, 2);
        b1.connect(&w4, 3);

        b1.connect(&w1, 1);  // Valid Connection: same wire to multiple bus wires
        b1.connect(&w1, 0);  // Invalid Connection: trying to connect a same wire to the same bus wire multiple times...
        std::cout << "\n";

        Bus<4> b2;
        w1.connect(&w2);
        w2.connect(&w3);
        w3.connect(&w4);
        w4.connect(&w1);
        std::cout << "\n";

        b2.connect(&b1.wire(0), 0);
        b2.connect(&b1.wire(1), 1);
        b2.connect(&b1.wire(2), 2);
        b2.connect(&b1.wire(3), 3);

        std::cout << "\nThese are my connected components:\n";
        for( size_t i = 0; i < b2.size(); i++ ) {
            for (auto& component : b2.myConnections(i)) {
                std::cout << "\t" << component->id() << " has ";
                auto connections = component->myConnections();
                for (auto& c : connections) {
                    std::cout << c->id() << " ";
                }
                std::cout << '\n';
            }
            std::cout << '\n';
        }
                     
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Component.h

#pragma once

#include <assert.h>
#include <memory>  
#include <array>
#include <list>       
#include <iomanip>
#include <iostream>    
#include <string>
#include <sstream>    

// I added this class template to handle the `ID's` as suggested to remove the code duplication...
template<class T>
class ComponentID {
public:
    explicit ComponentID(std::string& id) {
        static int i = 0;
        std::stringstream strValue;
        strValue << std::setw(3) << std::setfill('0') << std::to_string(i++);
        id.append("_" + strValue.str());
    }
};

class Component {
protected:
    std::string id_ = "";
    std::list<std::shared_ptr<Component>> components_;

    explicit Component(const std::string& id) : id_{ id } {}

public:
    virtual ~Component() {}

    std::string& id() { return id_; }

    void connect(Component* other) {
        for (auto& l : components_) {
            if (other->id_ == l->id()) {
                std::cout << "Component " << other->id_ << " already exists in " << id_ << "!\n";
                return;
            }
        }
        components_.push_back( std::make_shared<Component>( *other ) );
        std::cout << "Successfully connected " << other->id() << " to " << id_ << "\n";
    }

    virtual std::list<std::shared_ptr<Component>> myConnections() { return components_; }
    virtual void propagate() {};
};

Wire.h

#pragma once

#include "Component.h"

// I added ComponentID as an inherited base,
// I removed the `updateID()` function from this class,
// and I removed the call to it from within the constructor
class Wire : public Component, ComponentID<Wire> {
private:

public:
    explicit Wire(const std::string& name = "Wire") : Component(name), ComponentID(id_) {
        //updateId();
    };
       
    virtual ~Wire() {}

    virtual void propagate() override {
        return;
    }        
};

Now, this is where I'm starting to have some issues...

Bus.h

#pragma once

#include "Wire.h"

// I'm trying to do the same as I did in my Wire class through inheritance...
// I also removed the updateID() function and the call to it with the constructor
// However, this is generating a compiler error.
template<size_t BusSize>
class Bus : public Component, ComponentID<Bus> {
private:
    std::array<std::shared_ptr<Wire>, BusSize> bus_interface_;

public:
    explicit Bus(const std::string& name = "Bus") : Component(name), ComponentID(id_) {        
        createBus();
    }

    virtual ~Bus() {}

    virtual void propagate() override {
        return;
    }

    void connect(Component* component, size_t index) {
        assert(index < BusSize);
        assert(component != nullptr);
        bus_interface_[index]->connect(component);
        std::cout << "\t from " << component->id() << " to " << id_ << "\n";
    }

    Wire wire(size_t index) {
        assert(index < BusSize);
        return *bus_interface_[index].get();
    }

    std::array<std::shared_ptr<Wire>, BusSize> bus() { return bus_interface_; }

    std::list<std::shared_ptr<Component>> myConnections(size_t index) { 
        assert(index < BusSize);
        return bus_interface_[index]->myConnections();
    }

    size_t size() const { return BusSize; }

private:
    void createBus() {
        size_t i = 0;
        for (auto& w : bus_interface_) {
            w = std::shared_ptr<Wire>(new Wire);
        }
    }
};
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • Why is `ComponentID` a template class if it doesn't reference the `T` template parameter? – selbie Jul 08 '20 at 05:26
  • @selbie I was following the structure from the suggested answer within the original question... They have the ComponentID templated and then all of the sub Component classes are inheriting from that... And the ComponentID template argument is the class that is inheriting from it... This is what I'm understanding from their proposal... – Francis Cugler Jul 08 '20 at 05:29
  • In any case, I just pasted all your code into a single source file and compiled it. No issues. What version of Visual Studio are you on? – selbie Jul 08 '20 at 05:31
  • @selbie I want each subclass type to independently increment their own values that are appended to their string id... For example, I only have a Wire and a Bus class atm... So if I created 4 wires and 2 busses, then they should be as follows: `Wire_000`, `Wire_001`, `Wire_002`, `Wire_003`, `Bus_000`, `Bus_001` ... – Francis Cugler Jul 08 '20 at 05:32
  • Not sure if this has anything to do with it, but should `#include ` really be `#include ` ? I noticed that when I compiled your code - I had to fix that. – selbie Jul 08 '20 at 05:33
  • @selbie yes it's supposed to be ``... don't know how that got changed... In my IDE it is actually `` I think auto spell checker changed it because it did it when I tried to edit this comment... – Francis Cugler Jul 08 '20 at 05:34
  • @selbie Visual Studio finally gave me the info box... current version 15.9.20 Visual Studio 2017 – Francis Cugler Jul 08 '20 at 05:35
  • @selbie I have the language flag set to `/std:c++latest` and I'm compiling it as x64 Debug... – Francis Cugler Jul 08 '20 at 05:38
  • Well, it compiles fine on Visual Studio 2019 with all the latest patches. – selbie Jul 08 '20 at 05:42
  • @selbie Originally before I tried to apply CRTP, `id_` was already a member of `Component`. I understand the concept of `CRTP` but I'm not real familiar with using it directly... – Francis Cugler Jul 08 '20 at 05:42
  • @selbie I'm just not understanding... It works for the `Wire` class just fine... but when I try to use it with the `Bus` class, it's complaining because `Bus` itself is templated... and anything I try is failing... – Francis Cugler Jul 08 '20 at 05:47
  • @selbie, well there is an update available for 2017... I could install that, but that could take several hours, and I don't know if that will resolve it or not... – Francis Cugler Jul 08 '20 at 05:49
  • Please post the code that is failing not the code that is working – Alan Birtles Jul 08 '20 at 07:01

2 Answers2

1

Your second attempt at setting the base class is correct, you just need to add a call to the base class constructor as it doesn't have a default constructor:

template<size_t BusSize>
class Bus : public Component , ComponentID<Bus<BusSize>> {
private:
    std::array<std::shared_ptr<Wire>, BusSize> bus_interface_;

public:
    explicit Bus(const std::string& name = "Bus") : Component(name), ComponentID<Bus<BusSize>>(id_) {        
        createBus();
    }

Often a type alias is used to make this a bit simpler:

template<size_t BusSize>
class Bus : public Component , ComponentID<Bus<BusSize>> {
private:
    using BusComponentID = ComponentID<Bus<BusSize>>;
    std::array<std::shared_ptr<Wire>, BusSize> bus_interface_;

public:
    explicit Bus(const std::string& name = "Bus") : Component(name), BusComponentID(id_) {        
        createBus();
    }
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
0

I figured out a solution to my problem... I'm still puzzled as to why Visual Studio was giving me the compiler errors...

After looking at the code and seeing the pattern of the CRTP in use and without modifying it... I found an approach that works, and it works for me...

In my Bus class, it itself is templated but not with a typename type... and this got me to thinking and knowing that the current CRTP worked fine for the Wire class that was non templated... I elected to do the following...

I created another abstract base class... that is a middle layer between Component and Bus<size_t>... And it is this non templated abstract class that inherits from both Component and ComponentID. Then I just have Bus<size_t> inherit from BusAbstract.

The rest of my code is still the same except for some minor changes within their constructors for auto naming - id generation... Here is the result of my Bus class.

Bus.h

#pragma once

#include "Wire.h"

class BusAbstract : public Component, ComponentID<BusAbstract> {
protected:
    explicit BusAbstract(const std::string& name = "Bus") : Component(name), ComponentID(id_) {}
};

template<size_t BusSize>
class Bus : public BusAbstract {
private:
    std::array<std::shared_ptr<Wire>, BusSize> bus_interface_;

public:
    explicit Bus(const std::string& name = ("Bus<" + std::to_string(BusSize) + ">")  ) : BusAbstract(name) {        
        createBus();
    }

    virtual ~Bus() {}

    virtual void propagate() override {
        return;
    }

    void connect(Component* component, size_t index) {
        assert(index < BusSize);
        assert(component != nullptr);
        bus_interface_[index]->connect(component);
        std::cout << "\t from " << component->id() << " to " << id_ << "\n";
    }

    Wire wire(size_t index) {
        assert(index < BusSize);
        return *bus_interface_[index].get();
    }

    std::array<std::shared_ptr<Wire>, BusSize> bus() { return bus_interface_; }

    virtual std::list<std::shared_ptr<Component>> myConnections(size_t index) { 
        assert(index < BusSize);
        return bus_interface_[index]->myConnections();
    }

    size_t size() const { return BusSize; }

private:
    void createBus() {
        size_t i = 0;
        for (auto& w : bus_interface_) {
            w = std::shared_ptr<Wire>(new Wire(this->id() + "_Wire"));
        }
    }
};

And now I'm getting the desired behavior that I'm looking for without the "code-duplication" from the original question, and I'm able to do so with the use of this CRTP design.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59