#include <iostream>
#include <ostream>
#include <string>
#include <vector>
class Return_Self_Test {
std::string name;
public:
Return_Self_Test(std::string const& name_in) : name(name_in) { }
std::string const& get_name() const noexcept {
return name;
}
std::vector<Return_Self_Test> get_in_vector() const {
using std::literals::string_literals::operator""s;
std::vector<Return_Self_Test> rst;
rst.emplace_back(Return_Self_Test("new one 1"s));
rst.emplace_back(*this);
rst.emplace_back(Return_Self_Test("new one 2"s));
return rst;
}
};
std::ostream& operator<<(std::ostream& os, Return_Self_Test const& rst) {
return os << "Name: " << rst.get_name();
}
void return_self_test() {
Return_Self_Test rst_a("Original");
auto a_vect = rst_a.get_in_vector();
for (size_t i = 0; i < a_vect.size(); i++) {
std::cout << std::to_string(i) + ": " << a_vect[i] << '\n';
}
}
This is my suggestion for a slightly more organized solution of your problem. Let's walk this through and see what I did differently from you, and why.
Constructors
C++ is actually reasonably nice when it comes to what special member functions have to be declared by the user and which ones are generated by the compiler. For most simple classes, you actually don't need to define anything at all! Copy constructor, move constructor, copy assignment operator, move assignment operator and destructor are all implicitly defined in your case, so I went ahead and removed your copy constructor definition.
You should also notice that I removed that pointer-to-self constructor you defined, which you also expressed concerns about. I have good news for you: This constructor is completely unnecessary! In fact, your original code does not even use it, and neither does mine.
Where is to_string
?
I don't know why you felt compelled to write your own version of this function, but the standard provides something with the exact same functionality in the string
header (well, apart from the fact that std::to_string
will downright reject anything that is not a number).
Still, let's take some time and talk about your version of this common functionality. Three things come to my mind here:
- Why do you take
number
by const reference? You are expecting numeric types, which are all small enough to be efficiently passed as values.
- Having a normal
if
on a compile-time type trait is an slightly bad, if you have access to C++17. In that case, this should clearly be if constexpr (...)
.
- Regard and handle errors as errors. Don't return some ill-defined dummy value if your function does not succeed. There are many ways to signal an error, from throwing an exception to returning an
std::optional
. In this special case, however, the error is actually diagnosable at compile time. Although this makes point two useless, check your type at compile time and SFINAE out if it is not a number. You will save yourself a lot of debugging headaches down the line.
Doing I/O Correctly
Maybe you have been wondering: "Hey, where did my output
method go?". Probably, you have already realized that I turned it into a non-member operator<<
, and I do have good reasons to do so:
Imagine you are working with a huge project with thousands and thousand of classes. Your task is to implement a logging system: Sometimes, things go wrong, and it is your task to collect all the error messages that result from this, order them by certain criteria, and send them to various outputs (e.g. a log file, stdout, a web socket). However, all classes only offer a single output()
method, just as Return_Self_Test
does, which writes a message to std::cout
.
So you spend the next half a year doing the most boring, mind-numbing refactoring work anyone could possibly imagine.
Do you understand what I am getting at here? You are basically loading a gun to shoot yourself in the foot with in a few years when you write code like this. C++ offers a whole lot of flexibility through the whole istream
/ostream
concept, and you currently use exactly nothing of it.
This is why you should start writing operator<<
instead of your own output
or write
or whatever methods. This makes integrating output routines into an already existing context much more pleasant, as you can just write these values to std::cout
as you are used to with all of the basic types. Furthermore, using a general std::ostream
instead of std::cout
will save you a lot of suffering when you find yourself in a situation where you actually have to write to something that is not the standard output.
std::vector
, right? \$\endgroup\$