Eddy Pronk
Eddy Pronk

Reputation: 6695

How do write a template which can decide which constructor to call?

I'd like to be able to construct an A or B without having to think about the number of constructor arguments.

The second constructor is not legal C++ but I wrote it like this as an attempt to express what I want.

Is there an enable_if trick to selectively enable one of the constructors?

(e.g. depending on the number of constructor arguments of A and B.)

I need this to test about 15 classes with 1, 2 or 3 constructor arguments.

struct A
{
    A(int x)
    {
    }
};

struct B
{
    B(int x, int y)
    {
    }
};

template<typename T>
struct Adaptor // second constructor is illegal C++.
{
    T t;

    Adaptor(int x, int y)
        : t(x)
    {
    }

    Adaptor(int x, int y) // error: cannot be overloaded
        : t(x, y)
    {
    }
};

int main()
{
    Adaptor<A> a(1,2);
    Adaptor<B> b(1,2);

    return 0;
}

Upvotes: 1

Views: 295

Answers (3)

David Feurle
David Feurle

Reputation: 2787

Another variant:

#include <boost/utility/enable_if.hpp>

struct A {
    A(int) {}
};

struct B {
    B(int, int) {}
};

template <class T>
struct arg_count {
};

template <>
struct arg_count<A> {
    const static int count = 1;
};

template <>
struct arg_count<B> {
    const static int count = 2;
};

template <class T>
struct Adaptor : public T {
    template <class A1, class A2>
    Adaptor(A1 a1, A2 a2, typename boost::enable_if_c<arg_count<T>::count == 1, A1>::type* = 0) : T(a1) {}

    template <class A1, class A2>
    Adaptor(A1 a1, A2 a2, typename boost::enable_if_c<arg_count<T>::count == 2, A2>::type* = 0) : T(a1, a2) {}
};


int main() {
    Adaptor<A> a(1, 2);
    Adaptor<B> b(1, 2);
}

Upvotes: 2

Kerrek SB
Kerrek SB

Reputation: 477110

A variant of @Aaron's approach:

#include <type_traits>
#include <utility>

enum EArity { EZero = 0, EOne, ETwo, EThree, Error };

template <typename T, typename A1, typename A2, typename A3> struct getArity
{
    static const EArity arity =
       std::is_constructible<T>::value             ? EZero  :
       std::is_constructible<T, A1>::value         ? EOne   :
       std::is_constructible<T, A1, A2>::value     ? ETwo   :
       std::is_constructible<T, A1, A2, A3>::value ? EThree : Error;
};

template <typename T, EArity A> struct Construct;

template <typename T> struct Construct<T, EZero>
{
    T t;

    template <typename A1, typename A2, typename A3>
    Construct(A1 && a1, A2 && a2, A3 && a3) : t() { }
};

template <typename T> struct Construct<T, EOne>
{
    T t;

    template <typename A1, typename A2, typename A3>
    Construct(A1 && a1, A2 && a2, A3 && a3) : t(std::forward<A1>(a1)) { }
};

// ...

template <typename T>
struct AdapterIntIntInt : Construct<T, getArity<T, int, int, int>::arity>
{
    Adapter(int a, int b, int c)
    : Construct<T, getArity<T, int, int, int>::arity>(a, b, c) { }
};

template <typename T, typename A1, typename A2, typename A3>
struct Adapter : Construct<T, getArity<T, A1, A2, A3>::arity>
{
    Adapter(A1 && a1, A2 && a2, A3 && a3)
    : Construct<T, getArity<T, A1, A2, A3>::arity>
      (std::forward<A1>(a1), std::forward<A2>(a2), std::forward<A3>(a3))
    { }
};

Upvotes: 6

Aaron McDaid
Aaron McDaid

Reputation: 27133

Use template specialization. The first Adaptor is the 'default' template, but the latter will be used only for Adaptor<A>.

template<typename T>
struct Adaptor
{
    T t;

    Adaptor(int x, int y)
        : t(x,y)
    {
    }


};

template<>
struct Adaptor<A> {
    A t;
    Adaptor(int x, int y)
        : t(x)
    {
    }
};

Upvotes: 1

Related Questions