0

I have this sample program that works but isn't producing the desired results...

Here is the current output:

Output

class Component_00 class Component_01 class Component_02
Successfully connected class Component_01 to class Component_00
Successfully connected class Component_02 to class Component_00
Component class Component_02 already exists in class Component_00!
Successfully connected class Component_02 to class Component_01

And this would be my desired output:

Wire_00 Wire_01 Wire_02
Successfully connected Wire_01 to Wire_00
Successfully connected Wire_02 to Wire_00
Component Wire_02 already exists in Wire_00!
Successfully connected Wire_02 to Wire_01

This is a two-fold question but they are related to the same problem...

The first part is I don't want the word class to be printed before the actual class name.

The second part is I don't want the base class's name to be printed as this is an abstract class. I want the derived class's name to be printed instead...

What do I need to do to resolve this?

-Note- I am using Visual Studio 2017, but this should work agnostic of any compiler used, it should be portable.


Here is all of my relevant source code:

main.cpp

#include <iostream>
#include <exception>
#include "Wire.h"

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

        w1.connect(&w2);
        w1.connect(&w3);
        w1.connect(&w3);
        w2.connect(&w3);

    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Component.h

#pragma once

#include <list>
#include <map>
#include <memory>
#include <string>
#include <typeinfo>

class Component {
private:
    std::string id_ = std::string( typeid(*this).name() ) + "_0";
    std::list<Component*> components_;

public:
    Component() {
        updateId();
    }
    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_.emplace_back( other );
        std::cout << "Successfully connected " << other->id() << " to " << id_ << "\n";
    }

    virtual void propagate() = 0;

private:
    void updateId() {
        static int i = 0;
        id_.append( std::to_string(i++) );
    }
};

Wire.h

#pragma once

#include "Component.h"

class Wire : public Component {
private:

public:
    Wire() {}
    virtual ~Wire() {}

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


Edit

I modified my base and derived class to use a helper function in generating the class name...

Here are the modified classes:

Component.h

#pragma once

#include <list>
#include <map>
#include <memory>
#include <string>
#include <typeinfo>

template<class T>
constexpr const std::string generateName(T t) {
    return std::string(typeid(t).name());
}

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

public:
    explicit Component(const std::string& id) : id_{ id } {}
    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_.emplace_back( other );
        std::cout << "Successfully connected " << other->id() << " to " << id_ << "\n";
    }

    virtual void propagate() = 0;

protected:
    void updateId() {
        static int i = 0;
        id_.append( "_" + std::to_string(i++) );
    }
};

Wire.h

#pragma once

#include "Component.h"

class Wire : public Component {
private:

public:
    Wire() : Component(generateName(this)) {
        updateId();
    };
       
    virtual ~Wire() {}

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

The main cpp file has not changed...

Here is the new output...

class Wire *_0 class Wire *_1 class Wire *_2
Successfully connected class Wire *_1 to class Wire *_0
Successfully connected class Wire *_2 to class Wire *_0
Component class Wire *_2 already exists in class Wire *_0!
Succesfully connected class Wire *_2 to class Wire *_1

This is now giving me the desired base class's name, however, it is still printing the world class before it in which I don't want, and now it is also appending a space followed by an * in which I also don't want...

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • This program is ill-formed because `typeid` is used without including `` header. Moreover, typeinfo is being retrieved during construction. Rule of thumb: never use stuff that depends on vtable during construction. Rule of thumb #2: never use `typeid` and instead compile with RTTI disabled. Perhaps you should just add `Get_Name` virtual method. – user7860670 Jul 06 '20 at 19:47
  • @user7860670 I fixed the missing header include! – Francis Cugler Jul 06 '20 at 19:49
  • @user7860670 I don't have to use typeid... I could use a virtual method, could you provide an example as an answer, if it works for me I'll accept it. It's just that I'm not sure how else to retrieve the "class's" name to use it in the auto-generation of its id... – Francis Cugler Jul 06 '20 at 19:51
  • @user7860670 Also, I don't want to have to pass a const string to the constructor nor do I want it to "contain" another constant string just for the name of the class... I should be able to get this automatically and just use it... – Francis Cugler Jul 06 '20 at 19:54
  • @user7860670 I changed my class and added in a helper function to auto-generate the names... I'm a step closer, but the input still isn't exactly right... any tips or suggestions from here? – Francis Cugler Jul 06 '20 at 20:35
  • 1
    The output of `typeid(foo).name())` is not portable. Consider using something like [this](https://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template/59522794#59522794) instead (which is also not portable, but more bearable imho). – HolyBlackCat Jul 06 '20 at 20:46
  • If you like the names you get with Visual Studio you'll love what you get with gcc. – Retired Ninja Jul 06 '20 at 21:13
  • As a temporary localization fix for use with Visual Studio, I have modified my helper function to: `template constexpr const std::string generateName(T t) { const std::string s1{ "class " }; const std::string s2{ " *" }; std::string name = typeid(t).name(); name.erase(name.find(s1), s1.size()); name.erase(name.find(s2), s2.size()); return name; }` as this removes `"class "` before it and `" *"` after it... at least with this, I can move on with the rest of my classes and their implementation details... – Francis Cugler Jul 06 '20 at 21:34

1 Answers1

1

The following code (ripped from my RareCpp library) for getting type strings works on VS, gcc, and Clang, as far as I know there is no fully portable solution at this time, typeid makes no guarantee to generate a user friendly type name.

template <typename T>
constexpr auto getTypeView()
{
    std::string_view view;
#ifdef _MSC_VER
#ifndef __clang__
    view = __FUNCSIG__;
    view.remove_prefix(view.find_first_of("<")+1);
    view.remove_suffix(view.size()-view.find_last_of(">"));
#else
    view = __PRETTY_FUNCTION__;
    view.remove_prefix(view.find_first_of("=")+1);
    view.remove_prefix(view.find_first_not_of(" "));
    view.remove_suffix(view.size()-view.find_last_of("]"));
#endif
#else
#ifdef __clang__
    view = __PRETTY_FUNCTION__;
    view.remove_prefix(view.find_first_of("=")+1);
    view.remove_prefix(view.find_first_not_of(" "));
    view.remove_suffix(view.size()-view.find_last_of("]"));
#else
#ifdef __GNUC__
    view = __PRETTY_FUNCTION__;
    view.remove_prefix(view.find_first_of("=")+1);
    view.remove_prefix(view.find_first_not_of(" "));
    view.remove_suffix(view.size()-view.find_last_of("]"));
#else
    view = "unknown";
#endif
#endif
#endif
    return view;
}

template <typename T>
struct TypeName
{
    constexpr TypeName() : value() {
        auto view = getTypeView<T>();
        for ( size_t i=0; i<view.size(); i++ )
            value[i] = view[i];

        value[view.size()] = '\0';
    }
    char value[getTypeView<T>().size()+1];
};
    
template <typename T>
std::string TypeToStr() {
    return std::string(TypeName<T>().value);
}

You can then cut out the struct/class keyword and extra spacing with something like

std::string simplifyTypeStr(const std::string & typeStr) {
    std::string rawSimpleTypeStr = typeStr;
    if ( rawSimpleTypeStr.find("struct ", 0) != std::string::npos )
        rawSimpleTypeStr.erase(0, strlen("struct "));
    if ( rawSimpleTypeStr.find("class ", 0) != std::string::npos )
        rawSimpleTypeStr.erase(0, strlen("class "));

    std::string simpleTypeStr;
    for ( size_t i=0; i<rawSimpleTypeStr.size(); i++ ) {
        if ( rawSimpleTypeStr[i] != ' ' )
            simpleTypeStr += rawSimpleTypeStr[i];
        else if ( ++i < rawSimpleTypeStr.size() ) /* Remove space and upper-case the letter following the space */
            simpleTypeStr += std::toupper(rawSimpleTypeStr[i]);
    }
    return simpleTypeStr;
}

And cut out the pointer by removing it from the type using std::remove_pointer , your call from generate name would look like...

template<class T>
constexpr const std::string generateName(T t) {
    return simplifyTypeStr(TypeToStr<std::remove_pointer_t<T>>());
}

Copying that in with the rest of your code I got the output...

Wire_0 Wire_1 Wire_2
Successfully connected Wire_1 to Wire_0
Successfully connected Wire_2 to Wire_0
Component Wire_2 already exists in Wire_0!
Successfully connected Wire_2 to Wire_1

Edit: be sure to #include <string_view> and make sure your compiler is set to C++17 or higher (in VS this is under Project->Properties, "C++ Language Standard", set that to ISO C++17)

TheNitesWhoSay
  • 167
  • 1
  • 5
  • I like this solution and upvoted it. I haven't accepted it yet for I haven't tried it, but I'm sure it will work. I may put this in a utility class for string manipulations. I'll have to check it out when I have more time. This is only a personal project. I'm attempting to make a basic circuit simulator/emulator. I want to auto-generate the ids based on the class name and the instance count of that class. At the moment, all components are sequentially numbered regardless of their type since this is in the prototyping or drafting stage. I may change the auto-increment to be class independent. – Francis Cugler Jul 07 '20 at 06:11
  • ... After I work on this code a little more and possibly add in this functionality, if it works, I'll go ahead and accept your answer! When I was originally getting the unexpected results printed back from `typeid()` I figured it was the compiler's mangled name... and since each compiler implements things differently causing the class naming schemes to be compiler independent I knew there was no general or generic portable way to do this that was built into the language... However, this is something that I'm looking for to allow it to be portable to some degree! Thank You for the example! – Francis Cugler Jul 07 '20 at 06:16
  • I've been working on my project a little bit, and I chose to go down a different path. Instead of trying to get the name of the `class` from `typeid()` itself... I decided to give my subclasses' constructors an `std::string&` parameter that has a default value to the name of the class itself. This way if one calls the constructor with no parameters, it will use the default name, or they can pass their own string giving it their own unique naming scheme... I will still accept your answer as this is a valid solution to my original question! Thank you for the positive feedback! – Francis Cugler Jul 08 '20 at 03:31