Reputation: 10911
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
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