How does these code works ?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Bo Yang

    #1

    How does these code works ?

    Hi ,
    I am reading some boost code now , and
    I got confused by the following code in the
    add_reference type trait .

    template <class TT&(* is_reference_he lper1(wrap<T>) )(wrap<T>);
    char is_reference_he lper1(...);

    template <class Tno_type is_reference_he lper2(T&(*)(wra p<T>));
    yes_type is_reference_he lper2(...);

    template <typename T>
    struct is_reference_im pl
    {
    BOOST_STATIC_CO NSTANT(
    bool, value = sizeof(
    ::boost::detail ::is_reference_ helper2(

    ::boost::detail ::is_reference_ helper1(::boost ::type_traits:: wrap<T>())))
    == 1
    );
    };


    Why does function is_reference_he lper1 define in such
    a way , I can't understand what is it !

    Could someone explain these code for me ?
    Thank you very much !

  • Alf P. Steinbach

    #2
    Re: How does these code works ?

    * Bo Yang:
    Hi ,
    I am reading some boost code now , and
    I got confused by the following code in the
    add_reference type trait .
    >
    template <class TT&(* is_reference_he lper1(wrap<T>) )(wrap<T>);
    Yes, that /is/ confusing.

    The main problem is perhaps to determine the "innermost" thing declared,
    but as the famous Norwegian TV-cook Ingrid Espelid used to say, "but we
    have cheated and ... voilĂ !", and out of the oven she lifted a perfect
    .... something ... ; in this case, out of the oven comes the name
    is_reference_he lper1 (am I mixing metaphors?).

    Looking at both sides of that name, the "function call" argument list to
    the right takes precedence, it's done first, so to speak (although
    nothing is done, but you can read a declaration in much the same way as
    it would have been executed as an expression, from inner to outer).

    So, hypothetically doing that hypothetical function call, we find that
    is_reference_he lper1 must be a function taking a wrap<Targument, by value.

    Now for the result type, you need to work your way outwards in the
    declaration.

    And the first thing encountered then is the * on the left.

    As an expression that would mean a dereferencing of a pointer, and so in
    a declaration it means the thing declared /is/ a pointer (otherwise it
    couldn't be dereferenced). So the hypothetical function call yields a
    pointer. And so is_reference_he lper1 is a function returning a pointer.

    But a pointer to what?

    Going still farther outwards in the declaration, we have on the left
    'T&' and on the right '(wrap<T>)'. Again, if this were an expression
    the thing on the right would be a function call's argument list, and
    taking precedence. So we now know that the pointer is a pointer to a
    function, let's call it ResultFunc, that takes a wrap<Targument by value.

    Finally, the result type of ResultFunc is 'T&', a reference to an object
    of type T.

    So is_reference_he lper1 is a function that takes a wrap<Targument by
    value, and returns a pointer to a function (the ResultFunc) that takes a
    wrap<Targument by value and returns a reference to a T object.

    Incidentally, Google code search, applied to 'is_reference_h elper1', in
    addition to finding that line in the Boost library, turned up this
    description from the MSVC port of Andrei Alexandrescu's Loki Library:
    "is_reference_h elper1 is a function taking a Type2Type<Tretu rning a
    pointer to a function taking a Type2Type<Tretu rning a T&", which
    removes some of the mystery of what that 'wrap<T>' might be.

    char is_reference_he lper1(...);
    >
    template <class Tno_type is_reference_he lper2(T&(*)(wra p<T>));
    yes_type is_reference_he lper2(...);
    >
    template <typename T>
    struct is_reference_im pl
    {
    BOOST_STATIC_CO NSTANT(
    bool, value = sizeof(
    ::boost::detail ::is_reference_ helper2(
    >
    ::boost::detail ::is_reference_ helper1(::boost ::type_traits:: wrap<T>())))
    == 1
    );
    };
    Ah, well, I'll leave you to it! <g>

    Why does function is_reference_he lper1 define in such
    a way , I can't understand what is it !
    That's because Real Programmers can't be bothered with using 'typedef',
    or generally, naming things. After all, if the code could be understood
    by others, then one might soon be replaced by someone else. Okay,
    that's unfair, especially to Andrei who (it seems) bothered to write the
    clear explanatory comment; an equally probable explanation is that in
    the heat of the hunt, make that thing work, one has to try out many
    different things, and the less typing per thing tried the better, and
    then the first thing that works becomes embedded in stone, even if
    unreadable, because one is already moving on to the next problem...

    Could someone explain these code for me ?
    See above.

    Thank you very much !
    You're welcome.

    Cheers,

    - Alf

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?

    Comment

    • Bo Yang

      #3
      Re: How does these code works ?


      Alf P. Steinbach wrote:
      * Bo Yang:
      Hi ,
      I am reading some boost code now , and
      I got confused by the following code in the
      add_reference type trait .

      template <class TT&(* is_reference_he lper1(wrap<T>) )(wrap<T>);
      >
      Yes, that /is/ confusing.
      >
      The main problem is perhaps to determine the "innermost" thing declared,
      but as the famous Norwegian TV-cook Ingrid Espelid used to say, "but we
      have cheated and ... voilĂ !", and out of the oven she lifted a perfect
      ... something ... ; in this case, out of the oven comes the name
      is_reference_he lper1 (am I mixing metaphors?).
      >
      Looking at both sides of that name, the "function call" argument list to
      the right takes precedence, it's done first, so to speak (although
      nothing is done, but you can read a declaration in much the same way as
      it would have been executed as an expression, from inner to outer).
      >
      So, hypothetically doing that hypothetical function call, we find that
      is_reference_he lper1 must be a function taking a wrap<Targument, by value.
      >
      Now for the result type, you need to work your way outwards in the
      declaration.
      >
      And the first thing encountered then is the * on the left.
      >
      As an expression that would mean a dereferencing of a pointer, and so in
      a declaration it means the thing declared /is/ a pointer (otherwise it
      couldn't be dereferenced). So the hypothetical function call yields a
      pointer. And so is_reference_he lper1 is a function returning a pointer.
      >
      But a pointer to what?
      >
      Going still farther outwards in the declaration, we have on the left
      'T&' and on the right '(wrap<T>)'. Again, if this were an expression
      the thing on the right would be a function call's argument list, and
      taking precedence. So we now know that the pointer is a pointer to a
      function, let's call it ResultFunc, that takes a wrap<Targument by value.
      >
      Finally, the result type of ResultFunc is 'T&', a reference to an object
      of type T.
      >
      So is_reference_he lper1 is a function that takes a wrap<Targument by
      value, and returns a pointer to a function (the ResultFunc) that takes a
      wrap<Targument by value and returns a reference to a T object.
      >
      Incidentally, Google code search, applied to 'is_reference_h elper1', in
      addition to finding that line in the Boost library, turned up this
      description from the MSVC port of Andrei Alexandrescu's Loki Library:
      "is_reference_h elper1 is a function taking a Type2Type<Tretu rning a
      pointer to a function taking a Type2Type<Tretu rning a T&", which
      removes some of the mystery of what that 'wrap<T>' might be.
      >
      >
      Thank you very much .
      But , how does these code detect whether
      the typename T is a reference or not ?
      Although I understand what the code mean , but
      I still can't grasp how it tell whether T is a reference ?

      Comment

      • Alf P. Steinbach

        #4
        Re: How does these code works ?

        * Bo Yang:
        But , how does these code detect whether
        the typename T is a reference or not ?
        Although I understand what the code mean , but
        I still can't grasp how it tell whether T is a reference ?
        It's a case of SFINAE (Substitution Failure Is Not An Error). Consider
        that there are two declarations of is_reference_he lper1. Since the
        first one has result type T& it fails outright to be matched with T when
        T is a reference type, because you can't (currently, as of 2006) form a
        reference to a reference (SFINAE kicks in, we have failure, but not an
        error). That selects the second declaration of is_reference_he lper1,
        which has a nice, always valid 'char' result type, but argument type
        '...' which is the guaranteed worst match in an overload set, so that it
        won't be selected except when SFINAE kicks in for the first one.

        Because of the possible failure, and also because T might be any size,
        the size of the result type of is_reference_he lper1 cannot be used
        directly to compute the boolean yes/no of whether T is a reference type.
        But the type of the result can be used indirectly, namely to select
        from another overload set a function whose result type provides a
        distinguishable size. Hence the is_reference_he lper2 overloads, with
        argument types that match the two possible is_reference_he lper1
        functions, and easily distinguished result type sizes.

        Which result type is plugged into sizeof to make it all happen at
        compile time -- and of course, to determine the size, which determines
        which is_reference_he lper1 was selected, which determines whether T is a
        reference type or not.


        Hth. (it's a kind of rather ingenious TMP code, relying not only on
        undefined functions but also on constructs that won't compile --
        except when they're passed by via the SFINAE avoidance mechanism)

        - Alf

        --
        A: Because it messes up the order in which people normally read text.
        Q: Why is it such a bad thing?
        A: Top-posting.
        Q: What is the most annoying thing on usenet and in e-mail?

        Comment

        • Bo Yang

          #5
          Re: How does these code works ?


          Alf P. Steinbach wrote:
          * Bo Yang:
          >
          But , how does these code detect whether
          the typename T is a reference or not ?
          Although I understand what the code mean , but
          I still can't grasp how it tell whether T is a reference ?
          >
          It's a case of SFINAE (Substitution Failure Is Not An Error). Consider
          that there are two declarations of is_reference_he lper1. Since the
          first one has result type T& it fails outright to be matched with T when
          T is a reference type, because you can't (currently, as of 2006) form a
          reference to a reference (SFINAE kicks in, we have failure, but not an
          error). That selects the second declaration of is_reference_he lper1,
          which has a nice, always valid 'char' result type, but argument type
          '...' which is the guaranteed worst match in an overload set, so that it
          won't be selected except when SFINAE kicks in for the first one.
          >
          Because of the possible failure, and also because T might be any size,
          the size of the result type of is_reference_he lper1 cannot be used
          directly to compute the boolean yes/no of whether T is a reference type.
          But the type of the result can be used indirectly, namely to select
          from another overload set a function whose result type provides a
          distinguishable size. Hence the is_reference_he lper2 overloads, with
          argument types that match the two possible is_reference_he lper1
          functions, and easily distinguished result type sizes.
          >
          Which result type is plugged into sizeof to make it all happen at
          compile time -- and of course, to determine the size, which determines
          which is_reference_he lper1 was selected, which determines whether T is a
          reference type or not.
          >
          Thanks very much , I think I know these code fully .
          It is the T& which cause the substitution failed and detect whether T
          is a reference type .
          So we can just use template

          template <typename Tno_type is_reference_he lper( T& ) ;
          yes_type is_reference_he lper(...);

          to detect whether the T is a reference .
          But the code doesn't , does this just because the code don't
          want to construct the object T ?
          >
          Hth. (it's a kind of rather ingenious TMP code, relying not only on
          undefined functions but also on constructs that won't compile --
          except when they're passed by via the SFINAE avoidance mechanism)
          >
          I find C++ is so profound , I need more time to grasp the concept
          of Generic Programming .
          - Alf
          >
          --
          A: Because it messes up the order in which people normally read text.
          Q: Why is it such a bad thing?
          A: Top-posting.
          Q: What is the most annoying thing on usenet and in e-mail?

          Comment

          • Alf P. Steinbach

            #6
            Re: How does these code works ?

            * Alf P. Steinbach:
            * Bo Yang:
            >
            >But , how does these code detect whether
            >the typename T is a reference or not ?
            >Although I understand what the code mean , but
            >I still can't grasp how it tell whether T is a reference ?
            >
            It's a case of SFINAE (Substitution Failure Is Not An Error). Consider
            that there are two declarations of is_reference_he lper1. Since the
            first one has result type T& it fails outright to be matched with T when
            T is a reference type, because you can't (currently, as of 2006) form a
            reference to a reference (SFINAE kicks in, we have failure, but not an
            error). That selects the second declaration of is_reference_he lper1,
            which has a nice, always valid 'char' result type, but argument type
            '...' which is the guaranteed worst match in an overload set, so that it
            won't be selected except when SFINAE kicks in for the first one.
            >
            Because of the possible failure, and also because T might be any size,
            the size of the result type of is_reference_he lper1 cannot be used
            directly to compute the boolean yes/no of whether T is a reference type.
            But the type of the result can be used indirectly, namely to select
            from another overload set a function whose result type provides a
            distinguishable size. Hence the is_reference_he lper2 overloads, with
            argument types that match the two possible is_reference_he lper1
            functions, and easily distinguished result type sizes.
            >
            Which result type is plugged into sizeof to make it all happen at
            compile time -- and of course, to determine the size, which determines
            which is_reference_he lper1 was selected, which determines whether T is a
            reference type or not.
            >
            >
            Hth. (it's a kind of rather ingenious TMP code, relying not only on
            undefined functions but also on constructs that won't compile -- except
            when they're passed by via the SFINAE avoidance mechanism)
            >
            - Alf
            OTOH., I'm not sure I really understand the code (or rather, I'm
            beginning to strongly suspect I don't really understand it), because the
            following seemingly much simpler code, based on the principles explained
            above, seems to do the job:

            <code>
            #include <iostream>
            #include <ostream>
            #include <cstddef>

            std::size_t const largerThanPoint er = 666*sizeof(void *);

            template< typename Tstruct TypeCarrier {};

            template< typename TT* isRefHelper( TypeCarrier<T);
            char (&isRefHelper ( ... ))[largerThanPoint er];

            template< typename T >
            class IsRef
            {
            public:
            enum{ yes = (sizeof( isRefHelper( TypeCarrier<T>( ) ) ) ==
            largerThanPoint er ) };
            };

            int main()
            {
            typedef int NotRef;
            typedef int& Ref;

            std::cout << "Not ref yields " << IsRef<NotRef>:: yes << std::endl;
            std::cout << "Ref yields " << IsRef<Ref>::yes << std::endl;
            }
            </code>


            Perhaps it's time to yet again delve into Andrei's book "Modern C++
            Design" and check out the rationale... ;-)

            Cheers,

            - Alf

            --
            A: Because it messes up the order in which people normally read text.
            Q: Why is it such a bad thing?
            A: Top-posting.
            Q: What is the most annoying thing on usenet and in e-mail?

            Comment

            • Bo Yang

              #7
              Re: How does these code works ?


              Alf P. Steinbach wrote:
              * Alf P. Steinbach:
              * Bo Yang:
              But , how does these code detect whether
              the typename T is a reference or not ?
              Although I understand what the code mean , but
              I still can't grasp how it tell whether T is a reference ?
              It's a case of SFINAE (Substitution Failure Is Not An Error). Consider
              that there are two declarations of is_reference_he lper1. Since the
              first one has result type T& it fails outright to be matched with T when
              T is a reference type, because you can't (currently, as of 2006) form a
              reference to a reference (SFINAE kicks in, we have failure, but not an
              error). That selects the second declaration of is_reference_he lper1,
              which has a nice, always valid 'char' result type, but argument type
              '...' which is the guaranteed worst match in an overload set, so that it
              won't be selected except when SFINAE kicks in for the first one.

              Because of the possible failure, and also because T might be any size,
              the size of the result type of is_reference_he lper1 cannot be used
              directly to compute the boolean yes/no of whether T is a reference type.
              But the type of the result can be used indirectly, namely to select
              from another overload set a function whose result type provides a
              distinguishable size. Hence the is_reference_he lper2 overloads, with
              argument types that match the two possible is_reference_he lper1
              functions, and easily distinguished result type sizes.

              Which result type is plugged into sizeof to make it all happen at
              compile time -- and of course, to determine the size, which determines
              which is_reference_he lper1 was selected, which determines whether T is a
              reference type or not.


              Hth. (it's a kind of rather ingenious TMP code, relying not only on
              undefined functions but also on constructs that won't compile -- except
              when they're passed by via the SFINAE avoidance mechanism)

              - Alf
              >
              OTOH., I'm not sure I really understand the code (or rather, I'm
              beginning to strongly suspect I don't really understand it), because the
              following seemingly much simpler code, based on the principles explained
              above, seems to do the job:
              >
              <code>
              #include <iostream>
              #include <ostream>
              #include <cstddef>
              >
              std::size_t const largerThanPoint er = 666*sizeof(void *);
              >
              template< typename Tstruct TypeCarrier {};
              >
              template< typename TT* isRefHelper( TypeCarrier<T);
              char (&isRefHelper ( ... ))[largerThanPoint er];
              >
              I still can't understand why there must to be the return type
              to test the type , why to use a function's argument to test the
              type . just like

              template <typename Tvoid test( T& ) ;

              Comment

              • Alf P. Steinbach

                #8
                Re: How does these code works ?

                * Bo Yang:
                >
                I still can't understand why there must to be the return type
                to test the type , why to use a function's argument to test the
                type . just like
                >
                template <typename Tvoid test( T& ) ;
                Sorry, I don't understand the question.

                But a general solution to understanding something in programming is:
                just try to implement it.

                So that's what I advice.


                Cheers,

                - Alf

                --
                A: Because it messes up the order in which people normally read text.
                Q: Why is it such a bad thing?
                A: Top-posting.
                Q: What is the most annoying thing on usenet and in e-mail?

                Comment

                Working...