Using SFINAE with constructors

Discussion in 'C++' started by Clark S. Cox III, Sep 9, 2005.

  1. 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).

    --
    Clark S. Cox, III
    Clark S. Cox III, Sep 9, 2005
    #1
    1. Advertising

  2. Clark S. Cox III wrote:
    > 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
    Victor Bazarov, Sep 9, 2005
    #2
    1. Advertising

  3. In article <2005090915453516807%clarkcox3@gmailcom>,
    Clark S. Cox III <> wrote:

    > 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
    Howard Hinnant, Sep 9, 2005
    #3
    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. Dave Rudolf
    Replies:
    12
    Views:
    8,276
    Martijn Lievaart
    Feb 6, 2004
  2. Jeremy Smith
    Replies:
    2
    Views:
    587
    Jeremy Smith
    Aug 3, 2006
  3. Jess
    Replies:
    5
    Views:
    596
    Ron Natalie
    Jun 7, 2007
  4. Peng Yu
    Replies:
    5
    Views:
    393
    Juha Nieminen
    Sep 19, 2008
  5. srp113
    Replies:
    3
    Views:
    465
Loading...

Share This Page