There is a container class container
which is templated, so it can contain anything.
I want to add ability to print its contents to std::ostream
so I've overriden operator<<
.
However this has a drawback: if the container contains references (or pointers), the method only prints addresses instead of real information.
Please consider this simple test code which demonstrates the problem:
#include <iostream>
#include <deque>
template<typename T, bool is_reference = false>
class container {
public:
container() {}
void add(T a) { queue.push_back(a); }
friend std::ostream& operator<<(std::ostream& out, const container<T, is_reference>& c) {
for (const T& item : c.queue) {
if (is_reference) out << *item; else out << item;
out << ",";
}
return out;
}
private:
std::deque<T> queue;
};
int main() {
//Containers
container<int*, true> myContainer1;
container<int> myContainer2;
int myA1(1);
int myA2(10);
myContainer1.add(&myA1);
myContainer1.add(&myA2);
myContainer2.add(myA1);
myContainer2.add(myA2);
std::cout << myA1 << std::endl;
std::cout << myA2 << std::endl;
std::cout << myContainer1 << std::endl;
std::cout << myContainer2 << std::endl;
return 0;
}
I had an idea to provide an extra is_reference
boolean in the template for adjusting the operator<<
.
However this causes an early compiler error, in case I have value type container(s).
How can I make this work?
If I change the printer line to
out << item << ",";
The code compiles and prints this:
1
10
0x7ffd77357a90,0x7ffd77357a94,
1,10,
Obviously my goal is to have this result:
1
10
1,10,
1,10,
(How) can I achieve this easily?
CodePudding user response:
You can use SFINAE to select an overload based on whether std::is_pointer<T>::value
is true
or false
. This works already with C 11:
#include <iostream>
#include <deque>
template<typename T>
class container {
public:
// container() {} // dont define empty constructor when not needed
// or declare it as = default
void add(const T& a) { queue.push_back(a); } // should take const&
template <typename U = T, typename std::enable_if< std::is_pointer<U>::value,bool>::type=true>
friend std::ostream& operator<<(std::ostream& out, const container<T>& c) {
std::cout << "is pointer\n";
}
template <typename U = T, typename std::enable_if< ! std::is_pointer<U>::value,bool>::type=true>
friend std::ostream& operator<<(std::ostream& out, const container<T>& c) {
std::cout << "is not pointer\n";
}
private:
std::deque<T> queue;
};
int main() {
//Containers
container<int*> myContainer1;
container<int> myContainer2;
std::cout << myContainer1 << std::endl;
std::cout << myContainer2 << std::endl;
}
is pointer
is not pointer
Since C 17 you can use constexpr if
. And since C 14 (i believe) there are aliases std::is_pointer_v
and std::enable_if_t
that would make the code a little less verbose.
CodePudding user response:
For the conditional choice between item
and *item
to work, you will need a compiler that supports the C 17 Standard (or later) and then use an if constexpr (...)
statement.
Also, you can use std::is_pointer
to check whether the contained type is a pointer, rather than adding a 'flag' to your template:
#include <iostream>
#include <deque>
#include <type_traits>
template<typename T>
class container {
public:
container() {}
void add(T a) { queue.push_back(a); }
friend std::ostream& operator<<(std::ostream& out, const container<T>& c) {
for (const T& item : c.queue) {
if constexpr (std::is_pointer<T>::value) {
out << *item;
}
else {
out << item;
}
out << ",";
}
return out;
}
private:
std::deque<T> queue;
};