Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Near identical overloads with SFINAE in C++0X

Reply
Thread Tools

Near identical overloads with SFINAE in C++0X

 
 
Marc
Guest
Posts: n/a
 
      04-17-2011
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,;
}
 
Reply With Quote
 
 
 
 
Noah Roberts
Guest
Posts: n/a
 
      04-18-2011
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,;
> }


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
 
Reply With Quote
 
 
 
 
SG
Guest
Posts: n/a
 
      04-18-2011
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
 
Reply With Quote
 
Marc
Guest
Posts: n/a
 
      04-18-2011
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).
 
Reply With Quote
 
Noah Roberts
Guest
Posts: n/a
 
      04-18-2011
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
 
Reply With Quote
 
SG
Guest
Posts: n/a
 
      04-19-2011
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>(a rgs)...);
}

(untested)

SG
 
Reply With Quote
 
Richard Smith
Guest
Posts: n/a
 
      04-19-2011
On Apr 18, 9:15*pm, Marc <(E-Mail Removed)> 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
 
Reply With Quote
 
Marc
Guest
Posts: n/a
 
      04-20-2011
Richard Smith wrote:

> On Apr 18, 9:15*pm, Marc <(E-Mail Removed)> 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.
 
Reply With Quote
 
Noah Roberts
Guest
Posts: n/a
 
      04-20-2011
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
 
Reply With Quote
 
Richard Smith
Guest
Posts: n/a
 
      04-21-2011
On Apr 20, 7:11*pm, Noah Roberts <(E-Mail Removed)> 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
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
error C2665: 'delete' : none of the 2 overloads can convert parameter 1 from type 'const class xyz*' Angus C++ 2 01-07-2007 04:09 PM
error C2666 overloads have similar conversions wongjoekmeu@yahoo.com C++ 5 02-04-2005 07:23 PM
Python's idiom for function overloads Frans Englich Python 12 02-02-2005 10:56 PM
Has apparent 2.4b1 bug been fixed? flatten in Lib\compiler\ast.py overloads 'list' name Bengt Richter Python 3 01-19-2005 05:17 PM
Overloads modifier Fabio Negri Cicotti [MCP] ASP .Net 3 12-13-2004 05:20 PM



Advertisments