Adrian
Adrian

Reputation: 10911

Possible to write the code once for a specific set of different types?

I have a template class TC who's constructor takes parameters who's values are dependent on, as well as being of type Tn.

So, I want to create a helper template function htf that will call the same functions of a Tn object to generate a TC for a set of types X0 to Xn. The helper function takes only one parameter from that set. Is it possible, perhaps with variadic templates, to write the function once for the set of types, instead of having to write the same function over and over again for each type?

Now, I could just use a template to allow all types, but I don't want that as there may be another function with the same name written for a specific type later that's not based on this TC. And, IIRC I think SFINAE works with member functions, not pure functions.

This is just an idea in my head at the moment, that's why the question is very general. However, here is roughly the code I'm thinking of, simplified, in an more concrete and in an over generalized fashion:

struct X0
{
  int value;
  int& fn() { return value; }
};

struct X1
{
  double value;
  double& fn() { return value; }
};

struct X2
{
  float value;
  float& fn() { return value; }
};

struct Y0 // don't accept this class in helper function
{
  int value;
  int& fn() { return value; }
};

template<typename T1, typename Tn>
class TC
{
  T1* m_pT1;
  Tn* m_pTn;
  TC(T1* pT1, Tn* pTn) : m_pT1(pT1), m_pTn(pTn) {}
  friend TC htf(Tn& tn);
public:
  ~TC() {}
};

// concrete functions:
TC<int,    X0> htf(C0& x) { return TC<int,    X0>(&x.fn(), &x); }
TC<double, X1> htf(C1& x) { return TC<double, X1>(&x.fn(), &x); }
TC<float,  X2> htf(C2& x) { return TC<float,  X2>(&x.fn(), &x); }

// or in an over generalized template function but it'll accept
// Y0 and others which I don't want:
template<typename X>
auto htf(X& x) -> TC<decltype(x.fn()), X>
{
  return TC<decltype(x.fn()), X>(&x.fn(), &x);
}

So the htf function that I want is to work for classes X0, X1, and X2, but not Y0. However, I don't want it to interfere with any other function called htf that takes a parameter of type Y0, or any other type for that matter.

Additional

Is it possible to make it so that the collection of accepted classes can also include template classes taking an specified (or unspecified) number of parameters?

Upvotes: 0

Views: 90

Answers (2)

Adrian
Adrian

Reputation: 10911

This is an even more simplified version of my original question, but it relates to enabling a template function based on the type passed to it being part of a list of accepted types.

class A{};
class B{};
class C{};
class D{};
class collection1 : A, B, C {};
class collection2 : D {};

template<typename X>
typename std::enable_if<std::is_base_of<X, collection1>::value, X>::type fn(X x)
{
    return X();
}

Then the following would work appropriately:

fn(A()); // works
fn(B()); // works
fn(C()); // works
fn(D()); // compile time failure

Having a 2nd function like this:

template<typename X>
typename std::enable_if<std::is_base_of<X, collection2>::value, X>::type fn(X x)
{
    return X();
}

Would result in:

fn(A()); // works
fn(B()); // works
fn(C()); // works
fn(D()); // works

Using this method, I can enable function fn to work with types I want and not others and I can write the list with ease. Also, this should be faster than iterating through a list of variadic template parameters.

Thanks Jonathan Wakely, you helped a lot in my thought process. I just thought that this is simpler and can be made even clearer if I use a helper template which would encapsulate the enable_if clause which would be good as I have many other functions that would require this.

Additional

Looks like this answer isn't good enough as I need to be able to determine if a template class is in the collection I'm looking for.

Upvotes: 0

Jonathan Wakely
Jonathan Wakely

Reputation: 171263

Write a function that is only enabled when a trait is true, then specialize it for all the desired types.

template<typename T>
struct enable_htf : std::false_type { };

template<>
struct enable_htf<X0> : std::true_type { };

template<>
struct enable_htf<X1> : std::true_type { };

// etc.

template<typename T, bool enable = enable_htf<T>::value>
struct htf_helper { };

template<typename T>
struct htf_helper<T, true>
{
  using type = TC<decltype(std::declval<T&>().fn()), T>;
};

template<typename X>
typename htf_helper<X>::type
htf(X& x)
{
  return { &x.fn(), &x };
}

But it seems you want something like this instead:

template<typename Needle, typename... Haystack>
struct is_one_of;

template<typename Needle, typename Head, typename... Tail>
struct is_one_of<Needle, Head, Tail...>
: conditional<is_same<Needle, Head>::value, true_type,
              is_one_of<Needle, Tail...>>::type
{ };

template<typename Needle>
struct is_one_of<Needle> : false_type
{ };

template<typename X,
         typename Requires = typename enable_if<is_one_of<X, X0, X1, X2>::value>::type>
auto
htf(X& x) -> TC<decltype(x.fn()), X>
{
  return { &x.fn(), &x };
}

But personally I don't consider that clearer, even if is_one_of is reusable elsewhere.

Upvotes: 2

Related Questions