简体   繁体   中英

Could someone explain me the following template code?

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.

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&);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM