Trying to apply SFINAE

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Hendrik Schober

    Trying to apply SFINAE

    Hi,

    I'm having two overloaded function templates,

    #include <iterator>

    template< typename T >
    void test( T /*a1*/, T /*a2*/ ) {}

    template< typename Iter >
    void test( Iter /*b*/, Iter /*e*/ ) {}

    which I need to call. (In reality, these are constructors,
    in case that matters.) Unfortunately, the compiler isn't
    as clever and can't tell 'T' from 'Iter', so I have to
    give it a hint whether what gets passed is an iterator.
    But my attempt to use SFINAE for this

    template< typename Iter, class ItTr >
    void test( Iter /*b*/, Iter /*e*/
    , ItTr = std::iterator_t raits<Iter>() ) {}

    doesn't work, since this completely removed the iterator
    overload from the set the compiler considered. Or that's
    what I figured because this

    #include <iterator>

    //template< typename T >
    //void test( T /*a1*/, T /*a2*/ ) {}

    template< typename Iter, class ItTr >
    void test( Iter /*b*/, Iter /*e*/
    , ItTr = std::iterator_t raits<Iter>() ) {}

    int main()
    {
    const int fa[] = { 255, 255, 255, 255 };

    //test(0,1);
    test(fa, fa+4);

    return 0;
    }

    doesn't compile.
    Now I'm stumped. I thought I had done this before, so I am
    probably making something stupid here. (It's already passed
    6pm here...)

    Anyone out there?

    TIA,

    Schobi
  • Victor Bazarov

    #2
    Re: Trying to apply SFINAE

    Hendrik Schober wrote:
    [..]
    #include <iterator>
    >
    //template< typename T >
    //void test( T /*a1*/, T /*a2*/ ) {}
    >
    template< typename Iter, class ItTr >
    void test( Iter /*b*/, Iter /*e*/
    , ItTr = std::iterator_t raits<Iter>() ) {}
    >
    int main()
    {
    const int fa[] = { 255, 255, 255, 255 };
    >
    //test(0,1);
    test(fa, fa+4);
    >
    return 0;
    }
    >
    doesn't compile.
    [..]
    Well, the compiler cannot deduce the type from a default argument, I
    vaguely recall that it's a non-deducible context. That's why putting
    the traits there won't cut it. Consider

    #include <iterator>

    template<typena me Iter>
    void test(Iter, Iter, std::iterator_t raits<Iterconst * = 0);

    int main ()
    {
    const int fa[] = { 1,2,3,4 };
    test(fa, fa+4);
    }

    V
    --
    Please remove capital 'A's when replying by e-mail
    I do not respond to top-posted replies, please don't ask

    Comment

    • Hendrik Schober

      #3
      Re: Trying to apply SFINAE

      Victor Bazarov wrote:
      Hendrik Schober wrote:
      [...]
      >
      Well, the compiler cannot deduce the type from a default argument, I
      vaguely recall that it's a non-deducible context. That's why putting
      the traits there won't cut it. Consider
      >
      #include <iterator>
      >
      template<typena me Iter>
      void test(Iter, Iter, std::iterator_t raits<Iterconst * = 0);
      >
      int main ()
      {
      const int fa[] = { 1,2,3,4 };
      test(fa, fa+4);
      }
      Thanks! However, while the above works, this doesn't:

      #include <iterator>

      template< typename T >
      void test( T /*a1*/, T /*a2*/ ) {}

      template< typename Iter >
      void test( Iter /*b*/, Iter /*e*/
      , std::iterator_t raits<Iter= std::iterator_t raits<Iter>() ) {}

      int main()
      {
      const int fa[] = { 255, 255, 255, 255 };

      test(0,1);
      test(fa, fa+4);

      return 0;
      }

      (Both VC and Comeau complain it's ambiguous.) But that's
      just what I need.
      V
      Schobi

      Comment

      • Hendrik Schober

        #4
        Re: Trying to apply SFINAE

        James Kanze wrote:
        On Sep 29, 6:20 pm, Hendrik Schober <spamt...@gmx.d ewrote:
        >
        >I'm having two overloaded function templates,
        >
        > #include <iterator>
        >
        > template< typename T >
        > void test( T /*a1*/, T /*a2*/ ) {}
        >
        > template< typename Iter >
        > void test( Iter /*b*/, Iter /*e*/ ) {}
        >
        >which I need to call. (In reality, these are constructors,
        >in case that matters.)
        >
        It could be critical, if you need the initialization list.
        The problem is more with your below idea: I cannot
        (easily) forward calls.
        [...]
        But of course, since it's undefined behavior to instantiate
        iterator_trais with anything that is neither an iterator nor a
        pointer, you couldn't count on this even if the type deduction
        trick worked.
        Is it?
        I thought std lib implementors must have faced this,
        but now I see that this

        #include <vector>

        int main()
        {
        const unsigned int ua[] = { 255, 255, 255, 255 };

        std::vector<int v1(0u,1u);
        std::vector<int v2(ua, ua+4);

        return 0;
        }

        fails spectacularly with Dinkumware (both call the ctor
        taking iterators), so I must be wrong.

        Well, it seems I'm back to square one, then.
        Any ideas out there?

        Schobi

        Comment

        • Francesco

          #5
          Re: Trying to apply SFINAE

          Hi to all,
          maybe something like the following might work.
          Relying on the fact that an iterator should be either
          - a pointer
          - a class with some inner typedefs (here iterator_catego ry)
          The inspector class can be customized to better suit your needs...
          Maybe there is a simpler way... don't know :-)
          Bye,
          Francesco

          // code
          #include <iterator>
          #include <iostream>
          #include <vector>

          // ala boost::enable_i f
          template< typename T, bool K >
          class CTypeEnable;

          template< typename T >
          class CTypeEnable< T, false >
          {};

          template< typename T >
          class CTypeEnable< T, true >
          {
          public:
          typedef T tResult;
          };

          // ala boost::??
          template< typename T >
          struct CIsIter
          {
          typedef char (&tYes)[1];
          typedef char (&tNo)[2];

          template< typename T2 >
          static typename CTypeEnable< tYes,
          sizeof( typename T2::iterator_ca tegory ) >::tResult
          Check( T2 * );

          template< typename T2 >
          static tYes Check( T2 ** );

          static tNo Check( ... );

          enum { kResult = sizeof( Check( (T*) NULL ) )
          == sizeof( tYes ) };
          };

          // SFINAE with return type

          template< typename T >
          typename CTypeEnable< void, !CIsIter< T >::kResult >::tResult F( T )
          { std::cout << "NO ITERATOR\n"; }

          template< typename T >
          typename CTypeEnable< void, CIsIter< T >::kResult >::tResult F( T )
          { std::cout << "ITERATOR\n "; }

          struct A
          {};

          int main()
          {
          F( 10 );
          F( ( int*)NULL);
          std::vector< int vec;
          F( vec.begin() );
          F( A() );
          }
          //end code

          Hendrik Schober ha scritto:
          James Kanze wrote:
          On Sep 29, 6:20 pm, Hendrik Schober <spamt...@gmx.d ewrote:
          I'm having two overloaded function templates,
          #include <iterator>
          template< typename T >
          void test( T /*a1*/, T /*a2*/ ) {}
          template< typename Iter >
          void test( Iter /*b*/, Iter /*e*/ ) {}
          which I need to call. (In reality, these are constructors,
          in case that matters.)
          It could be critical, if you need the initialization list.
          >
          The problem is more with your below idea: I cannot
          (easily) forward calls.
          >
          [...]
          But of course, since it's undefined behavior to instantiate
          iterator_trais with anything that is neither an iterator nor a
          pointer, you couldn't count on this even if the type deduction
          trick worked.
          >
          Is it?
          I thought std lib implementors must have faced this,
          but now I see that this
          >
          #include <vector>
          >
          int main()
          {
          const unsigned int ua[] = { 255, 255, 255, 255 };
          >
          std::vector<int v1(0u,1u);
          std::vector<int v2(ua, ua+4);
          >
          return 0;
          }
          >
          fails spectacularly with Dinkumware (both call the ctor
          taking iterators), so I must be wrong.
          >
          Well, it seems I'm back to square one, then.
          Any ideas out there?
          >
          Schobi

          Comment

          • Victor Bazarov

            #6
            Re: Trying to apply SFINAE

            Hendrik Schober wrote:
            Victor Bazarov wrote:
            >Hendrik Schober wrote:
            >[...]
            >>
            >Well, the compiler cannot deduce the type from a default argument, I
            >vaguely recall that it's a non-deducible context. That's why putting
            >the traits there won't cut it. Consider
            >>
            > #include <iterator>
            >>
            > template<typena me Iter>
            > void test(Iter, Iter, std::iterator_t raits<Iterconst * = 0);
            >>
            > int main ()
            > {
            > const int fa[] = { 1,2,3,4 };
            > test(fa, fa+4);
            > }
            >
            Thanks! However, while the above works, this doesn't:
            >
            #include <iterator>
            >
            template< typename T >
            void test( T /*a1*/, T /*a2*/ ) {}
            >
            template< typename Iter >
            void test( Iter /*b*/, Iter /*e*/
            , std::iterator_t raits<Iter= std::iterator_t raits<Iter>()
            ) {}
            >
            int main()
            {
            const int fa[] = { 255, 255, 255, 255 };
            >
            test(0,1);
            test(fa, fa+4);
            >
            return 0;
            }
            >
            (Both VC and Comeau complain it's ambiguous.) But that's
            just what I need.
            They are probably correct :-/

            Let's try to involve a class where you can make a partial specialisation:

            template<class T, boolstruct ActualWorker; // "abstract"
            template<class Tstruct ActualWorker<T, false// non-iterator
            {
            static void test(T /*a1*/, T /*a2*/) { /*whatever*/ }
            };

            template<class Tclass ActualWorker<T, true// iterator
            {
            static void test(T /*i1*/, T /*i2*/) { /*whatever*/ }
            };

            // now - how do we determine it's an iterator?
            template<class Tstruct IsIterator { enum { yes = 0 }; };
            template<class Tstruct IsIterator {
            ... // here you need to add some way to set 'yes' to 1
            ... // if 'T' is an iterator. It's up to you to define
            ... // what is an iterator and what isn't.
            };

            template<class Tvoid test(T t1, T t2) {
            return ActualWorker<T, IsIterator<T>:: yes >::test(t1, t2);
            }

            int main()
            {
            test(42, 666);
            int foo[] = { 1,2,3,4 };
            test(foo, foo+4);
            }

            V
            --
            Please remove capital 'A's when replying by e-mail
            I do not respond to top-posted replies, please don't ask

            Comment

            • James Kanze

              #7
              Re: Trying to apply SFINAE

              On Sep 30, 11:58 am, Hendrik Schober <spamt...@gmx.d ewrote:
              James Kanze wrote:
              On Sep 29, 6:20 pm, Hendrik Schober <spamt...@gmx.d ewrote:
              I'm having two overloaded function templates,
              #include <iterator>
              template< typename T >
              void test( T /*a1*/, T /*a2*/ ) {}
              template< typename Iter >
              void test( Iter /*b*/, Iter /*e*/ ) {}
              which I need to call. (In reality, these are constructors,
              in case that matters.)
              It could be critical, if you need the initialization list.
              The problem is more with your below idea: I cannot
              (easily) forward calls.
              That's why I raised the issue of initialization lists. That's
              the main reason I can think of why you might not be able to
              forward calls. (It is, in fact, the only reason I can think of
              why you might not be able to forward calls.)
              [...]
              But of course, since it's undefined behavior to instantiate
              iterator_trais with anything that is neither an iterator nor a
              pointer, you couldn't count on this even if the type deduction
              trick worked.
              Is it?
              I think so. In §17.4.3.6/2, it says:

              In particular, the effects are undefined in the
              following cases:
              [...]
              -- for types used as template arguments when
              instantiating a template component, if the
              operations on the type do not implement the
              semantics of the applicable Requirements
              subclause.[...]

              I'm not sure, of course, because the description of Iterator
              traits (§24.3.1) doesn't actually contain a Requirements
              subclause. It does require a specific implementation, however
              (with a partial specialization for pointers), and that
              implementation uses typename Iterator::diffe rence_type, etc. So
              the presence of those types in the instantiation type would seem
              to be a requirement. (In this regared, the definition of
              iterator_catego ry is probably the most significant; I can't
              imagine anything but an iterator defining it.)

              In practice, what I would expect is that the code would fail to
              compile *IF* you use such an instantiation in a context which
              requires a complete definition. Or, if that failure occured
              during template type deduction, SFINAE. But I couldn't get it
              to work.
              I thought std lib implementors must have faced this,
              but now I see that this
              #include <vector>
              int main()
              {
              const unsigned int ua[] = { 255, 255, 255, 255 };
              std::vector<int v1(0u,1u);
              std::vector<int v2(ua, ua+4);
              return 0;
              }
              fails spectacularly with Dinkumware (both call the ctor
              taking iterators), so I must be wrong.
              The second is required to call the constructor taking iterators.
              The first shouldn't, however; the standard says that if the
              iterator type (determined by type deduction) is an integral
              type, the constructor shall has the same effect as:

              X( static_cast< typename X::size_type >( f ),
              static_cast< typename X::value_type >( l ) ) ;

              (This leads to some interesting, and possibly unintentional
              effects, due to the fact that static_cast is an explicit
              conversion. Thus:

              std::vector< std::vector< int v( 10, 30 ) ;

              works, although there is no implicit conversion of 30 to
              std::vector< int >.)

              The version of VC++ (which uses Dinkumware) which I have access
              to gets this right. Some older implementations of the library
              (Dinkumware or others), designed for compilers which didn't
              support member templates, may have problems, however.

              --
              James Kanze (GABI Software) email:james.kan ze@gmail.com
              Conseils en informatique orientée objet/
              Beratung in objektorientier ter Datenverarbeitu ng
              9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

              Comment

              • Hendrik Schober

                #8
                Re: Trying to apply SFINAE

                Victor Bazarov wrote:
                Hendrik Schober wrote:
                [...]
                >>
                > (Both VC and Comeau complain it's ambiguous.) But that's
                > just what I need.
                >
                They are probably correct :-/
                >
                Let's try to involve a class where you can make a partial specialisation:
                [example snipped]
                I knew how to solve this with introducing forwarding functions
                or additional class templates. I didn't want to change the code
                so much.
                Thanks nevertheless!
                V
                Schobi

                Comment

                • Hendrik Schober

                  #9
                  Re: Trying to apply SFINAE

                  James Kanze wrote:
                  On Sep 30, 11:58 am, Hendrik Schober <spamt...@gmx.d ewrote:
                  >James Kanze wrote:
                  >>On Sep 29, 6:20 pm, Hendrik Schober <spamt...@gmx.d ewrote:
                  [...]
                  >>>which I need to call. (In reality, these are constructors,
                  >>>in case that matters.)
                  >
                  >>It could be critical, if you need the initialization list.
                  >
                  > The problem is more with your below idea: I cannot
                  > (easily) forward calls.
                  >
                  That's why I raised the issue of initialization lists. That's
                  the main reason I can think of why you might not be able to
                  forward calls. (It is, in fact, the only reason I can think of
                  why you might not be able to forward calls.)
                  Ah, mea culpa. I didn't get this.
                  >>[...]
                  >>But of course, since it's undefined behavior to instantiate
                  >>iterator_trai s with anything that is neither an iterator nor a
                  >>pointer, you couldn't count on this even if the type deduction
                  >>trick worked.
                  >
                  > Is it?
                  >
                  I think so. In §17.4.3.6/2, it says:
                  [explanation snipped]
                  Thanks!
                  > I thought std lib implementors must have faced this,
                  > but now I see that this
                  >
                  > #include <vector>
                  >
                  > int main()
                  > {
                  > const unsigned int ua[] = { 255, 255, 255, 255 };
                  >
                  > std::vector<int v1(0u,1u);
                  > std::vector<int v2(ua, ua+4);
                  >
                  > return 0;
                  > }
                  >
                  > fails spectacularly with Dinkumware (both call the ctor
                  > taking iterators), so I must be wrong.
                  >
                  [...]
                  >
                  The version of VC++ (which uses Dinkumware) which I have access
                  to gets this right. Some older implementations of the library
                  (Dinkumware or others), designed for compilers which didn't
                  support member templates, may have problems, however.
                  I tried with VC9 (2008) and VC7.1 (2003). Both /seemed/ to fail,
                  but now that I dug deeper, I know that, while it indeed calls
                  the wrong ctor, that in turn forwards to some other function
                  and (by use of iterator tags) picks an overload of that which
                  does the right thing.
                  I'm sorry for spreading false information. :-x

                  Schobi

                  Comment

                  • Hendrik Schober

                    #10
                    Re: Trying to apply SFINAE

                    Francesco wrote:
                    Hi to all,
                    maybe something like the following might work.
                    [a lot of clever code snipped]
                    It might (except that ctors don't have a return type, but
                    that's curable), but it would introduce a lot of template
                    meta stuff into the code. This whole thing only came up
                    because I eliminated a bunch of warnings and I'm reluctant
                    to change too much well-tested code (I'd have to change
                    dozens of classes) at the heart of a quite sophisticated
                    domain-specific library I'm not very familiar with yet just
                    to get rid of a few warnings.
                    Thanks anyway. Yours is an interesting solution. It does
                    remind me that I still have to lobby for boost ('enable_if')
                    here. :)

                    Schobi

                    Comment

                    • Kai-Uwe Bux

                      #11
                      Re: Trying to apply SFINAE

                      Francesco wrote:
                      Hi to all,
                      maybe something like the following might work.
                      Relying on the fact that an iterator should be either
                      - a pointer
                      - a class with some inner typedefs (here iterator_catego ry)
                      The inspector class can be customized to better suit your needs...
                      Maybe there is a simpler way... don't know :-)
                      Bye,
                      Francesco
                      >
                      // code
                      #include <iterator>
                      #include <iostream>
                      #include <vector>
                      >
                      // ala boost::enable_i f
                      template< typename T, bool K >
                      class CTypeEnable;
                      >
                      template< typename T >
                      class CTypeEnable< T, false >
                      {};
                      >
                      template< typename T >
                      class CTypeEnable< T, true >
                      {
                      public:
                      typedef T tResult;
                      };
                      >
                      // ala boost::??
                      template< typename T >
                      struct CIsIter
                      {
                      typedef char (&tYes)[1];
                      typedef char (&tNo)[2];
                      >
                      template< typename T2 >
                      static typename CTypeEnable< tYes,
                      sizeof( typename T2::iterator_ca tegory ) >::tResult
                      Check( T2 * );
                      >
                      template< typename T2 >
                      static tYes Check( T2 ** );
                      >
                      static tNo Check( ... );
                      >
                      enum { kResult = sizeof( Check( (T*) NULL ) )
                      == sizeof( tYes ) };
                      };
                      >
                      // SFINAE with return type
                      >
                      template< typename T >
                      typename CTypeEnable< void, !CIsIter< T >::kResult >::tResult F( T )
                      { std::cout << "NO ITERATOR\n"; }
                      >
                      template< typename T >
                      typename CTypeEnable< void, CIsIter< T >::kResult >::tResult F( T )
                      { std::cout << "ITERATOR\n "; }
                      >
                      struct A
                      {};
                      >
                      int main()
                      {
                      F( 10 );
                      F( ( int*)NULL);
                      std::vector< int vec;
                      F( vec.begin() );
                      F( A() );
                      }
                      //end code

                      That idea can work a little simpler:


                      struct yes_type { char dummy; };
                      struct no_type { yes_type a; yes_type b; };

                      template < typename T >
                      struct is_iterator_typ e {

                      template < typename S >
                      static
                      yes_type check ( S*, typename S::iterator_cat egory* ptr = 0 );

                      template < typename S >
                      static
                      yes_type check ( S** );

                      static
                      no_type check ( ... );

                      public:

                      static bool const value =
                      sizeof( check((T*)0) ) == sizeof( yes_type );

                      };


                      #include <iostream>
                      #include <vector>

                      template < typename T >
                      void show ( void ) {
                      if ( is_iterator_typ e<T>::value ) {
                      std::cout << "is iterator type\n";
                      } else
                      std::cout << "is not iterator type\n";
                      }

                      #define SHOW(T) \
                      std::cout << #T << " "; \
                      show<T>();

                      int main() {
                      SHOW( std::vector<int >::const_iterat or );
                      SHOW( std::vector<int );
                      SHOW( int* );
                      SHOW( int );
                      }


                      Best

                      Kai-Uwe Bux

                      Comment

                      • Hendrik Schober

                        #12
                        Re: Trying to apply SFINAE

                        Kai-Uwe Bux wrote:
                        Francesco wrote:
                        [...]
                        >
                        That idea can work a little simpler:
                        >
                        [code snipped]
                        In my case it's even simpler, since I don't need the meta-function.
                        Here's what I came up with while commuting this morning:

                        struct yes_type { char dummy; };
                        struct no_type { char dummy1; char dummy2; };

                        template < typename T >
                        yes_type is_iterator(con st T&, typename T::iterator_cat egory* = 0)
                        {return yes_type();}

                        template < typename T >
                        yes_type is_iterator(con st T* const)
                        {return yes_type();}

                        no_type is_iterator(con st void* const)
                        {return no_type();}

                        no_type is_iterator(... )
                        {return no_type();}


                        #include <iostream>
                        #include <vector>

                        template < typename T >
                        inline void show( const T&, yes_type /*is_iterator*/)
                        {std::cout << "iterator: ";}

                        template < typename T >
                        inline void show( const T&, no_type /*is_iterator*/)
                        {std::cout << "no iterator: ";}

                        template < typename T >
                        inline void show(const char* str) {
                        show( T(), typename is_iterator(T() ) );
                        std::cout << str << '\n';
                        }

                        #define SHOW(T) show<T>(#T)

                        int main() {
                        SHOW( int* );
                        SHOW( int** );
                        SHOW( int* const* );
                        SHOW( const int* );
                        SHOW( const int** );
                        SHOW( const int* const* );
                        SHOW( std::vector<int >::const_iterat or );
                        std::cout << '\n';
                        SHOW( void* );
                        SHOW( std::vector<int );
                        SHOW( int );
                        }

                        However, this still needs a forwarding function... <sighs>
                        Kai-Uwe Bux
                        Schobi

                        Comment

                        Working...