Near identical overloads with SFINAE in C++0X

M

Marc

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&&...){}

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

#endif

int main(){
f(4,5);
f(6,7,8);
}
 
N

Noah Roberts

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&&...){}


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);
}
 
S

SG

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
 
M

Marc

SG said:
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).
 
N

Noah Roberts

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.
 
S

SG

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
 
R

Richard Smith

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
 
M

Marc

Richard said:
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.
 
N

Noah Roberts

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.
 
R

Richard Smith

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
 
M

Marc

Richard said:
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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top