user2269707
user2269707

Reputation:

Make template function select type by function parameter

I got some classes like

class A {
 public:
  void OnStageOne() {}
  void OnStageTwo() {}
  void OnStageThree() {}
};

class B {
 public:
  void OnStageOne() {}
  void OnStageTwo() {}
  void OnStageThree() {}
};

class C {
 public:
  void OnStageOne() {}
  void OnStageTwo() {}
  void OnStageThree() {}
};

and I got some vectors to store them:

std::vector<A> va;
std::vector<B> vb;
std::vector<C> vc;

now I need some operations like this: call OnStageXXX on all elements of a vector, so I wrote a template function like this:

template <typename T, void(T::*F)()>
void ForAll(std::vector<T> vector) {
  for (auto& item : vector) {
    (item.*F)();
  }
}

It works fine, except that I have to use syntax like this:

ForAll<A, &A::OnStageOne>(va);

Since the first template parameter can be deduced for the parameter va, is there any way I can make the code more cleaner? Like ForAll<OnStageOne>(va);?

Or, if this is an A/B question, this there any way I can wrote things like ForAll<OnStageXXX>(va); to call OnStageXXX on each element of a vector?

P.S. I tried with std::for_each, but this makes the syntax even longer. I'm here to seek for a shorter syntax to do this, if any.

Upvotes: 2

Views: 341

Answers (2)

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48457

In :

template <auto F, typename T>
void ForAll(std::vector<T>& vector) {
  for (auto& item : vector) {
    (item.*F)();
  }
}

Usage:

ForAll<&A::OnStageOne>(va);

DEMO


In :

constexpr auto OnStageOne = [] (auto& t) -> decltype(t.OnStageOne()) {
  return t.OnStageOne();
};
constexpr auto OnStageTwo = [] (auto& t) -> decltype(t.OnStageTwo()) {
  return t.OnStageTwo();
};
constexpr auto OnStageThree = [] (auto& t) -> decltype(t.OnStageThree()) {
  return t.OnStageThree();
};

template <auto F, typename T>
    requires requires (T t) { F(t); }
void ForAll(std::vector<T>& vector) {
  for (auto& item : vector) {
    F(item);
  }
}

Usage:

ForAll<OnStageOne>(va);

DEMO 2


In / (and before without the ranged based for loop):

struct OnStageOne {
  template <typename T>
  void operator()(T& t) const { t.OnStageOne(); }
};

struct OnStageTwo {
  template <typename T>
  void operator()(T& t) const { t.OnStageTwo(); }
};

struct OnStageThree {
  template <typename T>
  void operator()(T& t) const { t.OnStageThree(); }
};

template <typename F, typename T>
void ForAll(std::vector<T>& vector) {
  for (auto& item : vector) {
    F{}(item);
  }
}

Usage:

ForAll<OnStageOne>(va);

DEMO 3

Upvotes: 2

dfrib
dfrib

Reputation: 73186

This answer approaches the question from an "XY problem" perspective:

Or, if this is an A/B question, this there any way I can wrote things like ForAll(va); to call OnStageXXX on each element of a vector?

It seems that your intent is to implement static polymorphism for the classes A, B and C over a (not directly enforced) static interface, in this case, a number of OnStageXXX member functions. Instead of solving the problem of "how to (static-)polymorphically invoke via pointers to member functions" you may want to look over how you implement the "static interface" of you classes.

As a first step, instead of using separate non-template member functions for the OnStageXXX functions, you could implement them as a member function template parameterized over some tag, say an enum class StageTag. This would allow you to invoke the forAll(...) function using the tag instead of a pointer to member function to choose which member function specialization to invoke. E.g.:

#include <iostream>
#include <vector>

enum class StageTag { kOne, kTwo, kThree };

class A {
public:
  template <StageTag> void OnStage() const = delete;
};

template <> void A::OnStage<StageTag::kOne>() const {
  std::cout << "A OnStage kOne\n";
}

template <> void A::OnStage<StageTag::kTwo>() const {
  std::cout << "A OnStage kTwo\n";
}

template <> void A::OnStage<StageTag::kThree>() const {
  std::cout << "A OnStage kThree\n";
}

class B {
public:
  template <StageTag> void OnStage() const = delete;
};

template <> void B::OnStage<StageTag::kOne>() const {
  std::cout << "B OnStage kOne\n";
}

template <> void B::OnStage<StageTag::kTwo>() const {
  std::cout << "B OnStage kTwo\n";
}

template <> void B::OnStage<StageTag::kThree>() const {
  std::cout << "B OnStage kThree\n";
}

template <StageTag kTag, typename T> void ForAll(const std::vector<T> &vector) {
  for (auto &item : vector) {
    item.template OnStage<kTag>();
  }
}

int main() {
  const std::vector<A> va(3);
  const std::vector<B> vb(2);

  ForAll<StageTag::kThree>(va);
  // A OnStage kThree
  // A OnStage kThree
  // A OnStage kThree

  ForAll<StageTag::kOne>(vb);
  // B OnStage kOne
  // B OnStage kOne

  return 0;
}

Alternatively, instead of using specializations over StageTag, you could use tag dispatch to dispatch to the appropriate non-template member function:

#include <iostream>
#include <vector>

enum class StageTag { kOne, kTwo, kThree };

class A {
private:
  template <StageTag> struct ATag {};

public:
  template <StageTag kTag> void OnStage() const { OnStage(ATag<kTag>{}); }

private:
  void OnStage(ATag<StageTag::kOne>) const { std::cout << "A OnStage kOne\n"; }

  void OnStage(ATag<StageTag::kTwo>) const { std::cout << "A OnStage kTwo\n"; }

  void OnStage(ATag<StageTag::kThree>) const {
    std::cout << "A OnStage kThree\n";
  }
};

class B {
private:
  template <StageTag> struct BTag {};

public:
  template <StageTag kTag> void OnStage() const { OnStage(BTag<kTag>{}); }

private:
  void OnStage(BTag<StageTag::kOne>) const { std::cout << "B OnStage kOne\n"; }

  void OnStage(BTag<StageTag::kTwo>) const { std::cout << "B OnStage kTwo\n"; }

  void OnStage(BTag<StageTag::kThree>) const {
    std::cout << "B OnStage kThree\n";
  }
};

template <StageTag kTag, typename T> void ForAll(const std::vector<T> &vector) {
  for (auto &item : vector) {
    item.template OnStage<kTag>();
  }
}

int main() {
  const std::vector<A> va(3);
  const std::vector<B> vb(2);

  ForAll<StageTag::kThree>(va);
  // A OnStage kThree
  // A OnStage kThree
  // A OnStage kThree

  ForAll<StageTag::kOne>(vb);
  // B OnStage kOne
  // B OnStage kOne

  return 0;
}

You may also want to look into the Curiously Recurring Template Pattern to enforce your static interface.

Upvotes: 0

Related Questions