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...
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)