const struct object access member functions (and an operator overloading question) - c++

I tried to overload the << operator for my custom struct, but encountered error C2662, code as follows:
struct HP{
int max_hp;
int hp;
HP(int max_hp){
this->max_hp=max_hp;
this->hp=max_hp;
}
// this const declarative doesn't support const HP& obj argument
const string repr(){
stringstream hp_stats;
hp_stats << this->hp << "/" << this->max_hp;
return hp_stats.str();
}
// This is fine with const HP& obj argument
const string repr(){
stringstream hp_stats;
hp_stats << this->hp << "/" << this->max_hp;
return hp_stats.str();
}
};
// would cause error C2662
ostream& operator<<(ostream& out, const HP& obj){
out<<obj.repr();
return out;
}
I've found that this is because the implicitly access of this pointer, and const attempts to convert *this into const *this and thus fails. But as I changed from const string repr() to string repr() const, it magically worked and compiled successfully.
What's the difference between a const T func(){} and T func() const, which makes a const struct object invoking feasible?
I read it on cppreference.com, but still unsure why << overloading has to be declared outside a class scope.
Suppose I have repr() member function for struct HP, struct Shield, etc. I tried to make the << overloading a template function, but it turns that I am overloading << at global scope. Can I specify the T I am about to apply, OR apply the overloading across several classes with same member function, WITHOUT classes defined under one base class?
// complier thinks such overloading is global on <<
template <typename T>
ostream& operator<<(ostream& out, const T& obj){
out<<obj.repr();
return out;
}

const T func() {} means that the return type is const T and the function might mutate the object. Whereas, T func() const {} means that the return type is non-const but the object is unaltered (const). You can also have both or neither const.
It doesn't have to be declared outside the class, it can be declared inside as a friend (non-member) function. That works here as well. However, for a member function: since operator<<()'s first parameter is ostream&, you could only declare it in ostream and not HP, which won't work. Remember, member operators are like *this as the first argument of non-member operators.
Yes, you can do this. The easiest is to delegate to a common print function; the more elegant way is to use std::enable_if, e.g.:
template <typename T>
std::enable_if_v<std::is_same_v<T, HP> || std::is_same_v<t, Shield>, ostream&>
operator<<(ostream& out, const T& obj)
{
out<<obj.repr();
return out;
}
You can also write the conditions as a template and then re-use it:
template<typename T>
static constexpr bool is_printable_v = std::is_same_v<T, HP> || std::is_same_v<T, Shield>;
template <typename T>
std::enable_if_v<is_printable_v<T>>, ostream&>
operator<<(ostream& out, const T& obj) { ... }

Related

Fallback for "std::ostream" and "<<" operator using SFINAE and templates in C++17

I'm using Catch2 with TEST_CASE blocks from within I sometimes declare local temporary struct for convenience. These struct sometimes needs to be displayed, and to do so Catch2 suggests to implement the << operator with std::ostream. Unfortunately, this becomes quite complicated to implement with local-only struct because such operator can't be defined inline nor in the TEST_CASE block.
I thought of a possible solution which would be to define a template for << which would call toString() instead if that method exists:
#include <iostream>
#include <string>
template <typename T>
auto operator<<(std::ostream& out, const T& obj) -> decltype(obj.toString(), void(), out)
{
out << obj.toString();
return out;
}
struct A {
std::string toString() const {
return "A";
}
};
int main() {
std::cout << A() << std::endl;
return 0;
}
I have a few questions:
Is the decltype trick modern C++ or can we achieve the same using <type_traits> instead?
Is there a way to require for the toString() returned value to be a std::string and thus disable the template substitution otherwise?
Is it guaranteed that a class with a concrete implementation of operator<< will be prioritized over the template if it exists?
Also, I find this solution to be quite fragile (I get errors when compiling my overall project although this simple snippet works), and I think it can lead to errors because of its implicit nature. Unrelated classes may define toString() method without expecting it to be used in << template substitution.
I thought it might be cleaner to do this explicitly using a base class and then SFINAE:
#include <iostream>
#include <string>
#include <type_traits>
struct WithToString {};
template <typename T, typename = std::enable_if_t<std::is_base_of_v<WithToString, T>>>
std::ostream& operator<<(std::ostream& out, const T& obj)
{
out << obj.toString();
return out;
}
struct A : public WithToString {
std::string toString() const {
return "A";
}
};
int main() {
std::cout << A() << std::endl;
return 0;
}
The downside of this solution is that I can't define toString() as a virtual method in the base class otherwise it prevents aggregate initialization (which is super-useful for my test cases). Consequently, WithToString is just an empty struct which serves as a "marker" for std::enable_if. It does not bring any useful information by itself, and it requires documentation to be properly understood and used.
What are your thoughts on this second solution? Can this be improved somehow?
I'm targeting C++17 so I can't use <concepts> yet unfortunately. Also I would like to avoid using the <experimental> header (although I know it contains useful stuff for C++17).
You can think of both methods as "operator<< on all types with some property".
The first property is "has a toString()" method (and will work in C++11 even. This is still SFINAE, in this case the substitutions are in the return type). You can make it check that toString() returns a std::string with a different style of SFINAE:
template <typename T, std::enable_if_t<
std::is_same_v<std::decay_t<decltype(std::declval<const T&>().toString())>, std::string>,
int> = 0>
std::ostream& operator<<(std::ostream& out, const T& obj)
{
out << obj.toString();
return out;
}
And a non-template operator<< will always be chosen before this template. A more "specialized" template will also be chosen before this one. The rules for overload resolution are a bit complex, but they can be found here: https://en.cppreference.com/w/cpp/language/overload_resolution#Best_viable_function
The second property is "derives from WithToString". As you guessed, this one is more "explicit", and it is harder to accidentally/unexpectedly use the operator<<.
You can actually define the operator inline, with a friend function:
struct A {
std::string toString() const {
return "A";
}
friend std::ostream& operator<<(std::ostream& os, const A& a) {
return os << a.toString();
}
};
And you could also have this friend declaration in WithToString, making it a self-documenting mixin
template<typename T> // (crtp class)
struct OutputFromToStringMixin {
friend std::ostream& operator<<(std::ostream& os, const T& obj) {
return os << obj.toString();
}
};
struct A : OutputFromToStringMixin<A> {
std::string toString() const {
return "A";
}
};

Templated ostream operator << for multiple types within a namespace

I have a namespace with several structs and enum classes inside of it. For each type, I have a toString() method. Here is a small example:
namespace test {
struct A {
int i;
};
struct B {
float j;
};
std::string toString(const A &a){
return to_string(a.i);
}
std::string toString(const B &b){
return to_string(b.j);
}
}
I want to provide a templated operator<< which captures only these types, but not for types outside of this namespace:
template<class T>
std::ostream & operator<<(std::ostream &out, const T &t){
out << toString(t);
return out;
}
However, this gives me the following compilation error:
error: ambiguous overload for 'operator<<' (operand types are 'std::stringstream {aka std::__cxx11:basic_stringstream<char>}' and 'const char*')
How can I write a templated operator overload for this?
I solved it using concept & requires of C++20 (gcc >= 10.1):
template <typename T>
concept HaveToString = requires (T t) {
{ toString(t) };
};
template<HaveToString T>
std::ostream & operator<<(std::ostream &out, const T& t){
out << toString(t);
return out;
}
int main() {
test::A a;
std::cout << a << std::endl;
return EXIT_SUCCESS;
}
EDIT
For C++11:
template<typename T, typename = decltype(toString(std::declval<T>()))>
std::ostream & operator<<(std::ostream &out, const T& t){
out << toString(t);
return out;
}
Or as #MooingDuck mentioned in the comments:
template<typename T>
auto operator<<(std::ostream &out, const T& t) -> decltype(out<<toString(t)) {
out << toString(t);
return out;
}
Explanations
First of all, a really good article about unevaluated operands. It will help to understand what is going on in the expressions: decltype(toString(std::declval<T>())) and decltype(out<<toString(t)) which are both basically doing the same thing-> Setting a rule that any call to this function, have to support the call to toString function with the T parameter type.
First Approach
decltype(toString(std::declval<T>()))
Let's split this complex expression into sub expressions, from the inside out:
decltype(toString( std::declval<T>() ))
std::declval<T>() In some very simple words - means that we are "assuming" we created a variable of the type T at a compile time (If you didn't read the article yet, now it's a really good time to do so). The important thing to know before continue- we didn't do it, the important word is assuming.
decltype( toString(std::declval<T>()) )
The magic continue all the way up to decltype which checking for the type of the unevaluated expression within it. So, if toString that calls T type variable exists, it will return the value that toString function returns. If this function doesn't exist, a compile time error will be thrown (or in this context, the compiler won't deduce this function for the given type).
typename = decltype(toString(std::declval<T>()))
This section in the template meant to enable this function whenever the type returning from decltype is legal.
#MooingDuck Approach
auto operator<<(std::ostream &out, const T& t) -> decltype(out<<toString(t)) { /*...*/ }
Return value: auto
C++11: Deduced by the expression the after the operator ->.
After C++14: Calculated at compile time by the return expression inside the function (if there is no return expression, the return value deduced at compile time to void).
-> decltype(out<<toString(t))
Define the return value type.
decltype(out<<toString(t))
As explained before, whatever comes inside decltype is unevaluated expression. The compiler won't evaluate this expression, but it will make sure that the expression can be evaluated at runtime (or else an exception will be thrown, or in this case, the compiler won't deduce this function), and it will return the type of the returned value from this expression.

How to explicity call a templated overload of operator <<?

Consider the following example code for overloading the operator<< for a class A:
#include <iostream>
class A {
template <typename T>
friend A &operator<<(A &a, const T &t)
{
std::cout << t << std::endl;
return a;
}
friend A &operator<<(A &a, const std::string &t)
{
return operator<<<std::string>(a, t + "x");
}
};
My intention is that the second operator explicitly calls the first one.
However, in g++ 7.4 this fails with
In function 'A& operator<<(A&, const string&)':
error: 'operator<<' not defined
return operator<<<std::string>(a, t + "x");
^~
error: expected primary-expression before '>' token
return operator<<<std::string>(a, t + "x");
^
I however do not see why this should not compile.
Here is the code in godbolt.
In-class friend functions are not exposed to any scope. Friend injection used to be a thing (before ADL was invented), but now there is no way to call them except with ADL unless you declare them beforehand. In this case, a workaround is to declare the template function outside the class beforehand.
class A;
template <typename T>
A &operator<<(A &a, const T &t);
Call a function instead of calling an operator.
#include <iostream>
class A {
template <typename T>
static A &print(A &a, const T &t)
{
std::cout << t << std::endl;
return a;
}
template <typename T>
friend A &operator<<(A &a, const T &t)
{
return print(a, t);
}
friend A &operator<<(A &a, const std::string &t)
{
return print(a, t + "x");
}
};
You seem to wish to specialize your template function, but you're not doing it quite right. It should look more like this:
template <> friend A& operator<< <std::string>(A &a, const std::string &t)
{
// Print in here some how. It's not exactly clear to me how you intend to
// do this, as doing something like a << t will create infinite recursion
// finally, return a
return a;
}
Your other option is to switch the order of the functions, creating the template function after you create your first function:
friend A &operator<<(A &a, const std::string &t)
{
// Again, still not sure what you want to do here
// I just want to stress again though, don't do something
// like a << t, or operator<<(a,t)
// That will crash hard and fast, as there is no way to resolve
// it. It will create infinite recursion
return a;
}
template <typename T>
friend A &operator<<(A &a, const T &t)
{
std::cout << t << std::endl;
return a;
}
My intention is that the second operator explicitly calls the first one.
So, first, you will need to actually need the first option in that case.
Second, to do this, you will need to choose a type for t. You would do it like this:
operator<< <SomeType>(a,t);
Keep in mind, t will need to be implicitly converted to SomeType. Else, SomeType needs to be created by calling its constructor:
operator<< <SomeType>(a,SomeType(/* parameters to construct a SomeType ... */));
Note: Doing something like operator<< <SomeType>(a,t + "x") will always become infinitely recursive, and ultimately crash. This is because t + "x" is always an std::string. That means the compiler will always call this overload of the function infinitely, until it finally crashes from a stack overflow. So don't do that.

Overridden << operator not recognized

I'm trying to override the << operator but it seems that the compiler doesn't recognize my implementation and instead tries to interpret it as a bit shift.
I've already tried to play around with the parameter types (const T&, T&, T, const T) to no avail.
#pragma once
template<typename T> class AbstractStack
{
public:
virtual bool Push(const T &) = 0;
}
template <typename T> class ArrayStack : public AbstractStack <T>
{
public:
bool Push(const T&) {
....
}
}
template <typename T> bool operator<<(const AbstractStack<T>* &, const T&) {
return stack->Push(item);
}
int main() {
AbstractStack<int> *stack = new ArrayStack<int>(5);
int a = 2;
stack << a; // <<-- compiler error
return 0;
}
The error reported is:
Error (active) expression must have integral or unscoped enum type Lab10
Error C2296 '<<': illegal, left operand has type 'AbstractStack<int> *'
If I define the same operator acting on the class as a value, it just works...
When overloading operators, at least one of the arguments must be a class or an enum type - basically this allows/limits you to overloading custom types (user defined types).
From the cppreference;
When an operator appears in an expression, and at least one of its operands has a class type or an enumeration type, then overload resolution is used to determine the user-defined function to be called among all the functions whose signatures match the following...
This makes sense in that it disallows you from overloading the built in types; in this case, the pointer and integer you have as arguments.
As you already remarked in the question, the solution is taking your first argument by reference;
template <typename T>
bool operator<<(AbstractStack<T> &, const T&)
{ //...
Given the abstract base class you are looking to use, you could investigate the use of std::shared_ptr to help manage the resources and make the use of a "pointer" in the overloaded operator (albeit it will be a smart pointer);
template <typename T>
bool operator<<(std::shared_ptr<AbstractStack<T>>&, const T&)
{
return stack->Push(item);
}
int main() {
std::shared_ptr<AbstractStack<int>> stack = std::make_shared<ArrayStack<int>>(5);
int a = 2;
stack << a;
return 0;
}
As others have said, overloading any builtin operator requires an object of a user-defined type; a pointer won't work. And the solution is to use an object instead of a pointer:
template <typename T> bool operator<<(AbstractStack<T>&, const T&) {
return stack.Push(item);
}
and then call it with an object. There's no good reason in the code you've shown to allocate from the free-store; just create an auto object:
int main() {
ArrayStack<int> stack(5);
int a = 2;
stack << a;
return 0;
}

What does this "<>" mean inside a template class function?

There is a code like this.
const std::string DeviceTypeStrings[] ={ "A", "B", "C", "D", "E" };
enum DeviceTypes { A = 0, B, C, D, E };
template <DeviceTypes T> class DeviceType;
template <DeviceTypes T> std::ostream& operator<< (std::ostream& output, const DeviceType<T>& dev);
template <DeviceTypes T> class DeviceType {
public:
static const int value = T;
static const std::string string;
friend std::ostream & operator<< <>(std::ostream & output, const DeviceType<T> & deviceType );
};
template <DeviceTypes T> const std::string DeviceType<T>::string = DeviceTypeStrings[T];
template <DeviceTypes T> std::ostream & operator<< (std::ostream & output, const DeviceType<T> & deviceType ){
if ( DeviceType<T>::string.find(' ') != std::string::npos ){
return output << "\"" << DeviceType<T>::string << "\"";
} else {
return output << DeviceType<T>::string;
}
}
int main () {
DeviceType<A> myType;
std::cout << myType << std::endl;
return 0;
}
Note there is a "<>" after the operator<< inside class DeviceType, what does "<>" mean? If you could, why does it has to be there?
It simply means that the friend declaration refers to a particular specialization of function template operator << (declared previously), not to some yet undeclared ordinary non-template function operator <<.
Which specialization this friend declaration refers to is determined by the argument deduction mechanism, i.e. the actual template arguments are implicitly derived from the parameter types used in friend declaration. For this reason there's no need to specify template arguments in <> explicitly, but an empty pair of <> is still required.
In other words, the author of the code could've stated explicitly
friend std::ostream & operator<< <T>(std::ostream & output,
const DeviceType<T> & deviceType );
(note the explicit T in <T>). However, since the compiler can figure it out by itself (derive it from the type of the second argument), it is perefectly possible to put just an empty pair of <> there.
Now, if the code just said
friend std::ostream & operator<<(std::ostream & output,
const DeviceType<T> & deviceType );
(i.e. no <> at all), it would befriend an ordinary (non-template) function operator <<, which is not what the author wanted.
Overload resolution feature that works in this friend declaration can be illustrated without any friend declarations by the following simple example
void foo(int);
template <typename T> void foo(T);
int main() {
foo(42); // calls non-template function
foo<int>(42); // calls template function, explicit template argument given
foo<>(42); // calls template function, template argument deduced by compiler
}
When you want to tell the compiler that you specifically want to refer to a template version of the function, you have to include triangular brackets into the reference, even if there's nothing between them.
The compiler checks for < after operator<< if it's a friend function of a template class. It's considered as the template argument list.
It is same like at this link.
See the "Template specialization" chapter.
// template specialization
#include <iostream>
using namespace std;
// class template:
template <class T>
class mycontainer {
T element;
public:
mycontainer (T arg) {element=arg;}
T increase () {return ++element;}
};
// class template specialization:
template <>
class mycontainer <char> {
char element;
public:
mycontainer (char arg) {element=arg;}
char uppercase ()
{
if ((element>='a')&&(element<='z'))
element+='A'-'a';
return element;
}
};
int main () {
mycontainer<int> myint (7);
mycontainer<char> mychar ('j');
cout << myint.increase() << endl;
cout << mychar.uppercase() << endl;
return 0;
}
It's just how you refer to already defined template object (or function).