Using SFINAE with constructors

  • Thread starter Clark S. Cox III
  • Start date
C

Clark S. Cox III

I'm writing a class that, depending on a template parameter, has
constructors that take differing numbers of arguments. I initially
thought that I could use SFINAE (via boost::enable_if_c) to achieve my
ends, but I have hit a snag.

If I've messed up somewhere, or there's another way to get what I'm
after, I'd be greaful if someone could help me out.

Here is a condensed version of my code, showing the problem. Note that
I get the same problem regardless of whether FIRST_ATTEMPT is defined.

//--------------------------------
template<bool B, typename T>
struct Enabler
{
};

template<typename T>
struct Enabler<true, T>
{
typedef T type;
};


#if FIRST_ATTEMPT
template<unsigned N>
class Foo
{
public:
Foo() {}
explicit Foo(typename Enabler<N == 1, int>::type x) {}
Foo(typename Enabler<N == 2, int>::type x, int y) {}
Foo(typename Enabler<N == 3, int>::type x, int y, int z) {}
};
#else
template<unsigned N>
class Foo
{
struct private_type {};
public:
Foo() {}
explicit Foo(int x, typename Enabler<N == 1, private_type>::type =
private_type()) {}
Foo(int x, int y, typename Enabler<N == 2, private_type>::type =
private_type()) {}
Foo(int x, int y, int z, typename Enabler<N == 3, private_type>::type
= private_type()) {}
};
#endif

int main()
{
Foo<0> f0;
Foo<1> f1(1);
Foo<2> f2(1,2);
Foo<3> f3(1,2,3);

return 0;
}

//--------------------------------


The compiler tells me, several times that there is no type named ‘type’
in struct Enabler; which, I thought was the point of using SFINAE (i.e.
if there is no such type, then that overload is removed from the
resolution list).
 
V

Victor Bazarov

Clark said:
I'm writing a class that, depending on a template parameter, has
constructors that take differing numbers of arguments. I initially
thought that I could use SFINAE (via boost::enable_if_c) to achieve my
ends, but I have hit a snag.

If I've messed up somewhere, or there's another way to get what I'm
after, I'd be greaful if someone could help me out.

I think you're misinterpreting how SFINAE works. The point of it, IIUIC,
is that if it fails to find (deduce) the T for a template function, that
function is not instantiated and does not participate in the overload
resolution. So, it only works when you try to instantiate the template
where it is _used_, not where it's declared.

As to another way... It may be possible if all your arguments are of
built-in types (by use of the ellipsis), but if you want a generic
solution, you will need to use macros, probably.
Here is a condensed version of my code, showing the problem. Note that I
get the same problem regardless of whether FIRST_ATTEMPT is defined.

//--------------------------------
template<bool B, typename T>
struct Enabler
{
};

template<typename T>
struct Enabler<true, T>
{
typedef T type;
};


#if FIRST_ATTEMPT
template<unsigned N>
class Foo
{
public:
Foo() {}
explicit Foo(typename Enabler<N == 1, int>::type x) {}
Foo(typename Enabler<N == 2, int>::type x, int y) {}
Foo(typename Enabler<N == 3, int>::type x, int y, int z) {}
};
#else
template<unsigned N>
class Foo
{
struct private_type {};
public:
Foo() {}
explicit Foo(int x, typename Enabler<N == 1, private_type>::type =
private_type()) {}
Foo(int x, int y, typename Enabler<N == 2, private_type>::type =
private_type()) {}
Foo(int x, int y, int z, typename Enabler<N == 3, private_type>::type =
private_type()) {}
};
#endif

int main()
{
Foo<0> f0;
Foo<1> f1(1);
Foo<2> f2(1,2);
Foo<3> f3(1,2,3);

return 0;
}

//--------------------------------


The compiler tells me, several times that there is no type named ‘type’
in struct Enabler; which, I thought was the point of using SFINAE (i.e.
if there is no such type, then that overload is removed from the
resolution list).

Yes, but functions that _might_ participate but later are discarded from
the resolution list are all *declared*. SFINAE only engages when it is
impossible to deduce template arguments _and_ come up with valid results,
not when it's impossible to *declare* a function. SFINAE can only help
skip an instantiation of a template, but not a declaration of one.

V
 
H

Howard Hinnant

Clark S. Cox III said:
I'm writing a class that, depending on a template parameter, has
constructors that take differing numbers of arguments. I initially
thought that I could use SFINAE (via boost::enable_if_c) to achieve my
ends, but I have hit a snag.

If I've messed up somewhere, or there's another way to get what I'm
after, I'd be greaful if someone could help me out.

Here is a condensed version of my code, showing the problem. Note that
I get the same problem regardless of whether FIRST_ATTEMPT is defined.

//--------------------------------
template<bool B, typename T>
struct Enabler
{
};

template<typename T>
struct Enabler<true, T>
{
typedef T type;
};


#if FIRST_ATTEMPT
template<unsigned N>
class Foo
{
public:
Foo() {}
explicit Foo(typename Enabler<N == 1, int>::type x) {}
Foo(typename Enabler<N == 2, int>::type x, int y) {}
Foo(typename Enabler<N == 3, int>::type x, int y, int z) {}
};

Unfortunately once you instantiate a class template, you instantiate its
entire interface. SFINAE doesn't help you here. If the constructors
were member templates, then you could use SFINAE to constrain the free
template parameters on those members.

The only way I know of to get the effect you want is to specialize for
each value of N you are interested in. You could derive from an
implementation class to consolidate code that is common for all N:

template <unsigned N>
class FooImp
{
};

template<unsigned N> class Foo;

template <>
class Foo<0>
: private FooImp<0>
{
public:
Foo();
};

template <>
class Foo<1>
: private FooImp<1>
{
public:
Foo();
explicit Foo(int x);
};

template <>
class Foo<2>
: private FooImp<2>
{
public:
Foo();
Foo(int x, int y);
};

template <>
class Foo<3>
: private FooImp<3>
{
public:
Foo();
Foo(int x, int y, int z);
};

int main()
{
Foo<0> f0;
Foo<1> f1(1);
Foo<2> f2(1,2);
Foo<3> f3(1,2,3);

return 0;
}

-Howard
 

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,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top