Near identical overloads with SFINAE in C++0X

Discussion in 'C++' started by Marc, Apr 17, 2011.

  1. Marc

    Marc Guest

    Hello,

    I was surprised to realize that in the example below, only the first
    version is valid, whereas the second is considered a redefinition (which
    makes sense, but I hadn't expected it). Giving one of the overloads (but
    not the other of course) an extra ",class=void" template parameter makes
    it valid again.

    What would be your favorite workaround for this limitation?


    namespace std {
    template<bool,typename=void> struct enable_if{};
    template<typename T> struct enable_if<true,T>{typedef T type;};
    }

    #ifndef BUG

    template<class...U>
    typename std::enable_if<sizeof...(U)==2>::type
    f(U&&...){}

    template<class...U>
    typename std::enable_if<sizeof...(U)==3>::type
    f(U&&...){}

    #else

    template<class...U,class=typename std::enable_if<sizeof...(U)==2>::type>
    void f(U&&...){}

    template<class...U,class=typename std::enable_if<sizeof...(U)==3>::type
    // ,class=void
    >

    void f(U&&...){}

    #endif

    int main(){
    f(4,5);
    f(6,7,8);
    }
    Marc, Apr 17, 2011
    #1
    1. Advertising

  2. Marc

    Noah Roberts Guest

    On 2011-04-17 06:18, Marc wrote:
    > Hello,
    >
    > I was surprised to realize that in the example below, only the first
    > version is valid, whereas the second is considered a redefinition (which
    > makes sense, but I hadn't expected it). Giving one of the overloads (but
    > not the other of course) an extra ",class=void" template parameter makes
    > it valid again.
    >
    > What would be your favorite workaround for this limitation?
    >
    >
    > namespace std {
    > template<bool,typename=void> struct enable_if{};
    > template<typename T> struct enable_if<true,T>{typedef T type;};
    > }
    >
    > #ifndef BUG
    >
    > template<class...U>
    > typename std::enable_if<sizeof...(U)==2>::type
    > f(U&&...){}
    >
    > template<class...U>
    > typename std::enable_if<sizeof...(U)==3>::type
    > f(U&&...){}
    >
    > #else
    >
    > template<class...U,class=typename std::enable_if<sizeof...(U)==2>::type>
    > void f(U&&...){}
    >
    > template<class...U,class=typename std::enable_if<sizeof...(U)==3>::type
    > // ,class=void
    >>

    > void f(U&&...){}
    >
    > #endif
    >
    > int main(){
    > f(4,5);
    > f(6,7,8);
    > }


    I would not use enable_if for something like this.

    template < typename U, size_t SZ >
    struct f_impl
    {
    static void call(U&&);
    };

    template < typename U >
    struct f_impl<U,2>::call(U&&){}

    template < typename U >
    struct f_impl<U,3>::call(U&&){}

    template < typename U >
    void f(U && u)
    {
    typedef typename std::remove_reference<U>::type real_u_type; // !!!
    f_impl<U,sizeof(real_u_type)>::call(u);
    }

    --
    http://crazycpp.wordpress.com
    Noah Roberts, Apr 18, 2011
    #2
    1. Advertising

  3. Marc

    SG Guest

    On 18 Apr., 20:11, Noah Roberts wrote:
    >
    > template < typename U, size_t SZ >
    > struct f_impl
    > {
    >    static void call(U&&);
    > };
    >
    > template < typename U >
    > struct f_impl<U,2>::call(U&&){}
    >
    > template < typename U >
    > struct f_impl<U,3>::call(U&&){}
    >
    > template < typename U >
    > void f(U && u)
    > {
    >    typedef typename std::remove_reference<U>::type real_u_type; // !!!
    >    f_impl<U,sizeof(real_u_type)>::call(u);
    > }


    It seems you did not understand what Marc tried to do. Google for
    "variadic templates" and check out his code again. Also, your code
    lacks perfect forwarding. Instead, you used remove_reference for some
    reason and f_impl<>::call always takes an rvalue reference. But 'u' is
    an lvalue. So, it's never gonna work. There is a lot wrong with this
    code, actually. But the spirit is OK, I guess.

    SG
    SG, Apr 18, 2011
    #3
  4. Marc

    Marc Guest

    SG wrote:

    > It seems you did not understand what Marc tried to do. Google for
    > "variadic templates" and check out his code again. Also, your code
    > lacks perfect forwarding. Instead, you used remove_reference for some
    > reason and f_impl<>::call always takes an rvalue reference. But 'u' is
    > an lvalue. So, it's never gonna work. There is a lot wrong with this
    > code, actually. But the spirit is OK, I guess.


    Yes, I ignored the details and took it as: "dispatch instead of using
    sfinae", which is indeed doable in my context and a matter of taste (I
    am still not sure what soluton I'll settle on).
    Marc, Apr 18, 2011
    #4
  5. Marc

    Noah Roberts Guest

    On 2011-04-18 13:00, SG wrote:
    > On 18 Apr., 20:11, Noah Roberts wrote:
    >>
    >> template< typename U, size_t SZ>
    >> struct f_impl
    >> {
    >> static void call(U&&);
    >> };
    >>
    >> template< typename U>
    >> struct f_impl<U,2>::call(U&&){}
    >>
    >> template< typename U>
    >> struct f_impl<U,3>::call(U&&){}
    >>
    >> template< typename U>
    >> void f(U&& u)
    >> {
    >> typedef typename std::remove_reference<U>::type real_u_type; // !!!
    >> f_impl<U,sizeof(real_u_type)>::call(u);
    >> }

    >
    > It seems you did not understand what Marc tried to do. Google for
    > "variadic templates" and check out his code again.


    Don't know why the hostility. The OP's problem has nothing,
    specifically to do with variadic templates.

    Since you're so much better though, I welcome you to provide your own
    answer.

    --
    http://crazycpp.wordpress.com
    Noah Roberts, Apr 18, 2011
    #5
  6. Marc

    SG Guest

    On 18 Apr., 22:27, Noah Roberts wrote:
    >
    > Don't know why the hostility.


    I wouldn't say "hostility". It's just my honest feedback without any
    sugar coding. Pointing out that I *think* you misunderstood something
    and flaws of the code you presented is not hostile behaviour, is it?
    Maybe it was a little bit snarky. But what do you expect? The code did
    not work for several reasons.

    > The OP's problem has nothing,
    > specifically to do with variadic templates.


    Marc intended to enable/disable depending on sizeof...(U) where
    sizeof... is a new operator whith takes a parameter pack and returns
    its size.

    > [...] I welcome you to provide your own answer.


    I'd say, an improvement of your dispatching idea could look like this:

    template<int N> struct int2type
    { static const int value=N; };

    template<class...Args>
    void f_impl(int2type<2>, Args&&...args) {}

    template<class...Args>
    void f_impl(int2type<3>, Args&&...args) {}

    template<class...Args>
    inline void f(Args&&...args) {
    f_impl(int2type<sizeof...(Args)>(),forward<Args>(args)...);
    }

    (untested)

    SG
    SG, Apr 19, 2011
    #6
  7. On Apr 18, 9:15 pm, Marc <> wrote:
    >
    > Yes, I ignored the details and took it as: "dispatch instead of using
    > sfinae", which is indeed doable in my context and a matter of taste (I
    > am still not sure what soluton I'll settle on).


    One thing I would consider if there wasn't an obvious technical
    advantage to dispatch over SFINAE (or vice versa) is which documents
    the use of the code the best.

    So if f() is basically the same function in both cases, but you're
    using some clever optimisation that only works in the case of a three-
    member initialisation list, then I would probably use dispatch, as
    that makes it apparent from the interface that there's really just one
    function. (And if house style includes API documentation in the
    header, then I would only need to write it once.)

    But if the functions are have totally different semantics, then I'd
    use enable_if as this makes it clearer that the two versions are not
    simply an implementation detail, and that they have genuine
    differences, and could be separately documented.

    Of course, this is all personal preference, and the difference between
    the two scenarios isn't always clear cut. But I thought I'd share it
    anyway.

    Richard Smith
    Richard Smith, Apr 19, 2011
    #7
  8. Marc

    Marc Guest

    Richard Smith wrote:

    > On Apr 18, 9:15 pm, Marc <> wrote:
    >>
    >> Yes, I ignored the details and took it as: "dispatch instead of using
    >> sfinae", which is indeed doable in my context and a matter of taste (I
    >> am still not sure what soluton I'll settle on).

    >
    > One thing I would consider if there wasn't an obvious technical
    > advantage to dispatch over SFINAE (or vice versa) is which documents
    > the use of the code the best.
    >
    > So if f() is basically the same function in both cases, but you're
    > using some clever optimisation that only works in the case of a three-
    > member initialisation list, then I would probably use dispatch, as
    > that makes it apparent from the interface that there's really just one
    > function. (And if house style includes API documentation in the
    > header, then I would only need to write it once.)
    >
    > But if the functions are have totally different semantics, then I'd
    > use enable_if as this makes it clearer that the two versions are not
    > simply an implementation detail, and that they have genuine
    > differences, and could be separately documented.
    >
    > Of course, this is all personal preference, and the difference between
    > the two scenarios isn't always clear cut. But I thought I'd share it
    > anyway.


    In this case the 2 functions have different semantics, which might
    explain why I originally went the sfinae route. But at this stage,
    both implementations (sfinae with a tweak to avoid redefinition, or
    dispatching) seem fine.
    Marc, Apr 20, 2011
    #8
  9. Marc

    Noah Roberts Guest

    On 4/19/2011 10:22 AM, Richard Smith wrote:

    > But if the functions are have totally different semantics, then I'd
    > use enable_if as this makes it clearer that the two versions are not
    > simply an implementation detail


    I disagree and I would say that different semantics should mean
    different functions entirely. Neither enable_if nor dispatching is
    appropriate in that case.

    If you need to approach this semantic difference generically (which
    seems to me contradictory) then you can write a metafunction that will
    return/forward-to the appropriate function.

    --
    http://crazycpp.wordpress.com
    Noah Roberts, Apr 20, 2011
    #9
  10. On Apr 20, 7:11 pm, Noah Roberts <> wrote:
    > On 4/19/2011 10:22 AM, Richard Smith wrote:
    >
    > > But if the functions are have totally different semantics, then I'd
    > > use enable_if as this makes it clearer that the two versions are not
    > > simply an implementation detail

    >
    > I disagree and I would say that different semantics should mean
    > different functions entirely.  Neither enable_if nor dispatching is
    > appropriate in that case.


    That's not always possible. What if it's a constructor, for example?
    There's a good example in the C++ standard library:

    template <class T, class A> template <class I>
    std::vector<T, A>::vector( I first, I last, A const& = A() );

    At first sight, that's clear enough -- I is an iterator, and we simply
    want to copy data from [first,last). You can probably implement this
    more efficiently for Forward Iterators than for Input Iterators, but
    that's an implementation details which we'll ignore. But consider

    std::vector<long> v(10, 0);

    That should create a vector containing ten zeros, but it'll match the
    template constructor designed for iterators. You either solve that by
    dispatching within the template constructor, or by using SFINAE (e.g.
    with enable_if) to prevent the template constructor from matching in
    the first place.

    I certainly don't think it's *necessarily* bad design to have two
    potentially-similar overloads but that have totally different
    semantics, as in this case.

    Richard Smith
    Richard Smith, Apr 21, 2011
    #10
  11. Marc

    Marc Guest

    Richard Smith wrote:

    > On Apr 20, 7:11 pm, Noah Roberts <> wrote:
    >> I disagree and I would say that different semantics should mean
    >> different functions entirely.  Neither enable_if nor dispatching is
    >> appropriate in that case.

    >
    > That's not always possible. What if it's a constructor, for example?


    Thanks, that's precisely my case. The code in the original post had
    functions because there is some delegation going on, but the goal is a
    constructor. (and amusingly enough, the class has "vector" as part of
    its name ;-)

    Each of the 2 constructors makes sense individually, and if there was
    only one of them, I'd use enable_if on it to restrict possible
    ambiguities. It is only when I wrote both that I noticed it was
    forbidden for technical reasons.
    Marc, Apr 21, 2011
    #11
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Fabio Negri Cicotti [MCP]

    Overloads modifier

    Fabio Negri Cicotti [MCP], Dec 13, 2004, in forum: ASP .Net
    Replies:
    3
    Views:
    473
    William F. Robertson, Jr.
    Dec 13, 2004
  2. Replies:
    5
    Views:
    9,923
  3. Bengt Richter
    Replies:
    3
    Views:
    274
    Steve Holden
    Jan 19, 2005
  4. Frans Englich

    Python's idiom for function overloads

    Frans Englich, Feb 1, 2005, in forum: Python
    Replies:
    12
    Views:
    482
    Stephen Thorne
    Feb 2, 2005
  5. Angus
    Replies:
    2
    Views:
    1,472
    Ron Natalie
    Jan 7, 2007
Loading...

Share This Page