Adrian
Adrian

Reputation: 10911

Is there a way to have a template do specific things based on if parameter is a template type and value?

I would like to have a helper function or a templated struct do different things if passed a function pointer or a functor, but if I understand the template system correctly, this is not possible, since one is a type and the other is not.

Am I correct on that? I'd like to keep my API the same if possible, passing it either a functor or function pointer but not incur the cost of storing a pointer if not needed. E.g.:

template <typename T, void(*fn)(T)>
struct X1
{
  T m_t;

  X(T t) : m_t(t) {}
  void invoke() { fn(m_t); }
};

template <typename T, typename F>
struct X2
{
  T m_t;
  F m_f;

  X(T t, F f) : m_t(t), m_f(f) {}
  void invoke() { m_f(m_t); }
};

So in that case, I'd either like to have these structs sharing the same name, or have helper functions instantiate the appropriate struct with either a functor or a function pointer placed at the same parameter position with the same decorations around the function/functor.

Something like:

void fn(int) {}
auto functor = [](int){};
int h = 3;

auto x1 = make_X(h, fn);
auto x2 = make_X(h, functor);

Where make_X() is the link that I'm looking for. Ideas?

Upvotes: 2

Views: 179

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275936

The single responsibility principle says that the optimization around function pointers being stateless, and the rest of your code, should be handled separately.

So, create a stateless functor that calls fn in one part of your code:

template<class Sig, Sig* fn>
auto stateless() {
  return [](auto&&...args)->decltype(auto){
    return fn(decltype(args)(args)...);
  };
}

this is C++14. In C++11, you just have to write the equivalent of that lambda manually.1

empty base optimization helper makes storing empty instances of types cheap and somewhat easy:

template<class Tag, class T, class=void>
struct ebo {
  ebo( T tin ):t(std::forward<T>(tin)) {}
  T t;
  T& get( Tag ){ return t; }
  T const& get( Tag ) const { return t; }
  ebo(ebo&&)=default;
  ebo(ebo const&)=default;
  ebo&operator=(ebo&&)=default;
  ebo&operator=(ebo const&)=default;
  ebo()=default;
};
template<class Tag, class T>
struct ebo<Tag, T,
  std::enable_if_t<std::is_empty<T>{} && !std::is_final<T>{}>
>:
  T
{
  ebo( T tin ):T(std::forward<T>(tin)) {}
  T& get( Tag ){ return *this; }
  T const& get( Tag ) const { return *this; }
  ebo(ebo&&)=default;
  ebo(ebo const&)=default;
  ebo&operator=(ebo&&)=default;
  ebo&operator=(ebo const&)=default;
  ebo()=default;
};

And implement X with the above:

struct F_tag {};
template <typename T, typename F>
struct X:ebo<F_tag, F>
{
  X(T tin, F fin):
    ebo<F_tag,F>(std::forward<F>(fin)),
    m_t(std::forward<T>(tin))
 {}

  T m_t;
  void invoke() { this->get(F_tag{})(m_t); }
};
template<class T, class F>
X<T,F> make_X( T t, F f ) {
  return {std::forward<T>(t),std::forward<F>(f)};
}

At point of use, this looks like:

void fn(int) {}
auto functor = [](int){};
int h = 3;

auto x1 = make_X(h, stateless<void(int),&fn>());
auto x2 = make_X(h, functor);

zero bytes are used to store either the fn(int) or functor.

I take by value: adding forwarding references is left as an exercise.


1 C++11 version of stateless:

template<class Sig, Sig* fn>
struct stateless_t {
  template<class...Ts>
  auto operator()(Ts&&...ts)const
  -> decltype( fn(std::declval<Ts>()...) )
  { return fn(std::forward<Ts>(ts)...); }
};
template<class Sig, Sig* fn>
stateless_t<Sig, fn> stateless() { return {}; }

Upvotes: 2

Related Questions