while experimenting with the answer from This post, ended up with the following piece of code:
#include <iostream>
#include <typeinfo>
namespace Test
{
struct MyPtr
{
double operator, (int Input)
{
std::cout << "Operator, called" << std::endl;
return 1.0;
}
MyPtr operator* ()
{
std::cout << "operator* called" << std::endl;
return *this ;
}
MyPtr operator ()
{
std::cout << "operator called" << std::endl;
return *this;
}
MyPtr operator!= (const MyPtr& Other)
{
std::cout << "operator!= called" << std::endl;
return *this;
}
};
struct Foo
{
MyPtr Ptr;
};
MyPtr begin(Foo& t)
{
return t.Ptr;
}
MyPtr end(Foo& t)
{
return t.Ptr;
}
}
int main()
{
std::cout << typeid(decltype( begin(std::declval<Test::Foo&>()),
*begin(std::declval<Test::Foo&>()),
std::true_type{})).name() <<std::endl;
}
which yields:
d
Here, d comes from the comma operator. However, as the last expression inside the decltype specifier is std::true_type{}, why does the decltype specifier resolves to the type returned by the comma operator instead of std::true type.
My Theory is that std::true_type can be implicitly cast into an int, which operator,() here takes as a parameter, hence the decltype expression is equivalent to:
std::cout << typeid(decltype( begin(std::declval<Test::Foo&>()),
declval<double&>())).name() <<std::endl;
Can you confirm this is correct?
What i am planning on doing when i will have a better grasp of the compiler's behavior, i plan on replacing the contents of the decltype specifier with:
std::cout << typeid(decltype(void( begin(std::declval<Test::Foo&>())),
void(*begin(std::declval<Test::Foo&>())),
std::true_type{})).name() <<std::endl;
which will prevent operator,() from matching, even if it is overridden. Can you also confirm this is correct?
CodePudding user response:
I added a memebr to MyPtr
to get more inforamtion. Then actually evaluating the expression tells you what happens:
#include <iostream>
#include <typeinfo>
struct MyPtr {
int value;
MyPtr(int x) : value(x) {}
double operator,(int Input) {
std::cout << "Operator, called value = " << value
<< " Input = " << Input << std::endl;
return 1.0;
}
MyPtr operator*() {
std::cout << "operator* called value = " << value << std::endl;
return *this;
}
MyPtr operator () {
std::cout << "operator called value = " << value << std::endl;
return *this;
}
MyPtr operator!=(const MyPtr& Other) {
std::cout << "operator!= called" << std::endl;
return *this;
}
};
struct Foo { MyPtr Ptr; };
MyPtr begin(Foo& t) { return t.Ptr; }
MyPtr end(Foo& t) { return t.Ptr; }
int main() {
Foo f1{10};
Foo f2{20};
begin(f1), *begin(f2), std::true_type{};
begin(f1), *begin(f2), std::false_type{};
}
operator called value = 10
operator* called value = 20
Operator, called value = 20 Input = 1
operator called value = 10
operator* called value = 20
Operator, called value = 20 Input = 0
Your "Theory" is correct. Lets remove the
and *
to make it simpler, then you can think of f1,f2,std::true_type{}
as f1.operator,( f2.operator,(std::true_type{})
. std::true_type
has operator bool
and bool
can be implcicitly converted to int
. To illustrate that I also added the example with false_type
in the code.
The operator bool
can be found here as operator value_type
, because std::true_type
is just an alias for std::integral_constant<bool,true>
.
which will prevent operator,() from matching, even if it is overridden. Can you also confirm this is correct?
Yes. It is not possible to implement an comma operator overload for void
.