0

Reading the Chapter 22 of C++ templates, Second Edition, I try to understand the implementation of the EqualityComparable trait. But I cannot understand how the compiler decide to activate the fallback or not.

In addition to this there are two function that have only declared but the program compiles and runs. This is strange to me.

Here is the code. The header file IsEqualityComparable.hpp

#include <utility>        // for declval()
#include <type_traits>    // for true_type and false_type

template<typename T>
class IsEqualityComparable
{
 private:
 // test convertibility of == and ! == to bool:
 static void* conv(bool);  // to check convertibility to bool
 template<typename U>
 static std::true_type test(decltype(conv(std::declval<U const&>() ==
                                        std::declval<U const&>())),
                          decltype(conv(!(std::declval<U const&>() ==
                                          std::declval<U const&>())))
                         );
// fallback:
template<typename U>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(nullptr,
                                             nullptr))::value;
};

The source file is the following

#include <iostream>
#include <exception>
#include <utility>
#include <functional>
#include "isequalitycomparable.hpp"

template<typename T,
     bool EqComparable = IsEqualityComparable<T>::value>
struct TryEquals
{
  static bool equals(T const& x1, T const& x2) {
  std:: cout << "IsEqualityComparable equals::"<<std::endl;
  return x1 == x2;
}
};
class NotEqualityComparable : public std::exception
{
};
template<typename T>
struct TryEquals<T, false>
{
  static bool equals(T const& x1, T const& x2) {
  std:: cout << "Throw::"<<std::endl;
  throw NotEqualityComparable();
}
};
void foo(int)
{
} 
void bar(int)
{
}
class A
{
 public:
 A() = default;
 friend bool operator ==(A a1 , A a2)
 {
    return true;
 }
};
int main()
{
 std:: cout << "Enter" << std::endl;
 std::function<void(int)> f = foo;
 std::function<void(int)> f2 = f;
 std:: cout << "Enter" << std::endl;
 //std:: cout << "Check::"<< 
 //TryEquals<std::function<void(int)>>::equals(f,f2) << std::endl;
 A a1;
 A a2;
 std:: cout << "Check::"<< TryEquals<A>::equals(a1,a2) << std::endl;
 return 0;
}

The

TryEquals<std::function<void(int)>>::equals(f,f2)

throws an exception because the operator == is not implemented but

TryEquals<A>::equals(a1,a2)  

returns 1 because the class A has an operator ==.

In this point I need help to understand how the conv and test work.

Moreover how does the

static constexpr bool value = decltype(test<T>(nullptr,
                                               nullptr))::value

works?

I confused with this expression

decltype(test<T>(nullptr,nullptr))::value. 
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
getsoubl
  • 808
  • 10
  • 25

1 Answers1

1

The functions do not need to be defined, because they are never actually called.

decltype is an Unevaluated context where it figures out the return type of the function, but never tries to compute a return value.

In this case it is combined with sfinae, so that if decltype cannot figure out the return type of == (probably because the operator doesn't exist) that overload of test will be ignored. And then the test(...) will be selected instead.

This uses the fact that ... is the absolute worst match for a parameter type, so it will be used only if there are no other overloads available (thus "fallback").

And by the way, std::declval is never defined either.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • Hello, Thank you for the prompt reply . I understood your point. – getsoubl Jun 03 '18 at 09:40
  • A final question about decltype(test(nullptr,nullptr))::value .How this is reduced to boolean.Thank you – getsoubl Jun 03 '18 at 10:45
  • The `void* conv(bool);` would require its parameter to be convertible to `bool`. This guards against a perverted `void operator==(U,U)`. *Really* covering all bases. :-) – Bo Persson Jun 03 '18 at 10:52
  • Maybe my question was confusing. I try to understand how the following decltype(test(nullptr,nullptr)) is deduced to the true_type trait. I have understood the usage of the test and conv. I really appreciate that you spend time on answering – getsoubl Jun 03 '18 at 13:03
  • 1
    Again, that is the `conv` function that returns `void*`. So if an operator is present, `decltype(conv(something))` will result in `void*`. And, if so, we get a function `test(void*, void*)` which is a good match for a call `test(nullptr, nullptr)`. Then we use `decltype` on the `test` call, and get `true_type` if the first version exists, otherwise `false_type` from the fallback. – Bo Persson Jun 03 '18 at 13:24
  • 1
    And to your defence - in not getting it immediately - this meta programming idiom has taken decades to evolve and has many, many contributors on the way. – Bo Persson Jun 03 '18 at 13:28