0

I am new to template programming and I intend to use the solution here to make sure that the types used have an operator defined. But I would like to understand this code. I looked up information of cppreference but I am more confused on how this works.

How to check whether operator== exists?

Unfortunately it is quite cryptic to me and thought would ask for the meaning and reasons for some of the things in the code.

namespace CHECK
{
    struct No {};
    template <typename T, typename Arg>
    No operator==(const T&, const Arg&);

    /* Why are there two definitions of structures? Is this needed at all? Is
     * there a simpler version of this whole code snippet?
     */
    template <typename T, typename Arg = T>
    struct EqualExists {
        /* Why does this have to be a enum? What effect does this have? */
        enum {
            /* What happens if == sign is not defined for T? What would the
             * below comparison return when true/false? (The comparison *(T*)(0) == *(Arg*)(0))
             *
             * What is the need for No here? Why is it needed and how will
             * comparing it with the return type of == comparison be true ever?
             * How can the return type of == comparison and No structure be the same?
             */
            value = !std::is_same<decltype(*(T*)(0) == *(Arg*)(0)), No>::value
        };
    };
}

Could someone link this to the original question? Hope this helps someone new to cpp to understand this as well.

underscore_d
  • 6,309
  • 3
  • 38
  • 64
  • What are the outermost `{}`? A namespace, probably? – aschepler Nov 18 '19 at 03:47
  • Yes. Let me fix it. Sorry about that – theCuriousOne Nov 18 '19 at 03:48
  • 1
    Or you can accept another template argument of type bool. If you need 2-3 switches, use an integer template argument and check bits. If you need more, use a class template argument, with a bunch of constexpr members to switch parts of your code. `if constexpr` is readable, C++ template metaprogramming is not. – Soonts Nov 18 '19 at 07:01
  • "_Could someone link this to the original question?_" The fact that you included a link to the other question in yours achieves that already: https://stackoverflow.com/questions/linked/6534041?sort=newest – underscore_d Nov 19 '19 at 14:21

1 Answers1

2

Explanation

Let's break it down step-by-step:

  • (T*)(0) : pretend there's an object of type T at address 0; the type of this subexpression is a pointer
  • *(T*)(0) : pretend there's an object of type T at address 0; the type of this subexpression is a reference, due to the asterisk in front dereferencing the pointer. It looks kind of like a null pointer dereference, but more on that later.
  • *(Arg*)(0) : pretend there's an object of type Arg at address 0; same pattern as T
  • *(T*)(0) == *(Arg*)(0) : this is equivalent to calling operator==<T, Arg>( *(T*)(0), *(Arg*)(0) ) with the additional convenience of letting the compiler figure out where the operator is defined.
    • If a user-defined operator== does not exist, then the CHECK namespace operator will be matched instead. It's a "catch all" template.
  • decltype(*(T*)(0) == *(Arg*)(0)) : decltype says "don't execute the subexpression in the parentheses; just give me its type". In this case, the type is the return type of the operator== call.
    • No comparison operation actually occurs, nor any derefences into memory.
  • std::is_same<decltype(*(T*)(0) == *(Arg*)(0)), No>::value : std::is_same's value is true if the types are identical, or false otherwise.
    • ::value is a static constexpr bool
    • The two type arguments are the decltype(...) and struct CHECK::No.
    • A real-world operator== typically returns bool. Occasionally it may return a user-defined type. It's unlikely that someone will write their custom operator== to return CHECK::No, and this code is relying upon that assumption.
  • enum { value = !std::is_same<...>::value } : An enum is always a compile-time constant, works on older C++ compilers & specs (like C++03, where constexpr didn't exist), is compatible with constexpr, and does not require storage.
    • static constexpr bool value = !std::is_same<...>::value; would have been equivalent.

Issues with the example code:

  • Technically it's illegal to dereference a null pointer; std::declval is a safe alternative.
  • Technically someone could write CHECK::No operator==(const Foo&, const Bar&), which would fool the check into thinking the operator was undefined.
  • The operator== in the CHECK namespace could shadow a globally defined operator== definition, resulting in a false negative.

Alternative Implementation

To fix the above issues and "simplify", one method is to use SFINAE. Although "simpler" is subjective here.

[edit] Link to working version: http://coliru.stacked-crooked.com/a/e9cc48729d53b6c6 , and updated code below

    template <typename T, typename Arg = T>
    class EqualExists {
        template <class U = T, class V = Arg, bool Exists = !!sizeof(std::declval<U>() == std::declval<V>())>
        static std::true_type Func(const T&, const Arg&);

        template <class U, class V>
        static std::false_type Func(const U&, const V&);

    public:
        static constexpr bool value = decltype(Func(std::declval<T>(), std::declval<Arg>()))::value;
    };

[edit] My original answer had a bug : did not use template args U and V in the Exists computation, and did fail to compile on gcc. (worked on msvc for some reason)

        template <class U, class V, bool Exists = !!sizeof(std::declval<T>() == std::declval<Arg>())>
        static std::true_type Func(const T&, const Arg&);
fifoforlifo
  • 724
  • 5
  • 9
  • @fifoforlife thank you so much for the explanation. Things are a lot more clearer now. "If a user-defined operator== does not exist, then the CHECK namespace operator will be matched instead. It's a "catch all" template." --> Does this mean that if == operator or type T is not defined then it would use the == operator defined in CHECK namespace? What would this be? Assuming this is some default operator defined by compiler would'nt we always get true from std::is_same? – theCuriousOne Nov 19 '19 at 03:40