This question is inspired from my previous question No template parameter deduction of parameter pack.
Consider following code example:
#include <memory>
#include <string>
template<typename... FArgs>
class Callback
{
public:
class Handle{};
};
class BaseCallbackHandle
{
};
using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;
template<typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle( H handle)
{
return {};
}
int main()
{
Callback<int>::Handle h;
std::string s;
makeTypeErasedCallbackHandle(h); //should compile fine
makeTypeErasedCallbackHandle(s); //should raise a compile error
}
See also http://coliru.stacked-crooked.com/a/5f2a2e816eef6afd
The function template makeTypeErasedCallbackHandle
now takes any class as input parameter. Is there any way to ensure (e.g. with static-assert or enable_if), that only Callback<FArgs...>::Handle
(with any FArgs
) is allowed as H
? The example with Callback<int>::Handle
shall compile, while std::string
shall fail.
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>(float original); Template arguments may be omitted when the compiler can infer them.
Deriving from a non-template base class There is no requirement that your base class be a template. It is quite possible to have a template class inherit from a 'normal' class. This mechanism is recommended if your template class has a lot of non-template attributes and operations.
A non-template class can have template member functions, if required. Notice the syntax. Unlike a member function for a template class, a template member function is just like a free template function but scoped to its containing class.
Define a type within your Handle
class, and refer to that type inside makeTypeErasedCallbackHandle()
:
#include <memory>
#include <string>
template <typename... FArgs>
struct Callback {
struct Handle {
using callback_type = Callback<FArgs...>;
};
};
struct BaseCallbackHandle {
};
using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;
template <typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H handle) {
using callback_type = typename H::callback_type;
return {};
}
int main() {
Callback<int>::Handle h;
std::string s;
makeTypeErasedCallbackHandle(h); //should compile fine
makeTypeErasedCallbackHandle(s); //should raise a compile error
}
Live example
This will fail during instantiation for any H
that doesn't define the nested type.
With a little more effort, you can static_assert
to produce a meaningful message to the client, while at the same time increasing the flexibility of the solution via type traits. This has the advantage that callback_impl::is_callback
can be specialised for arbitrary handle types:
#include <memory>
#include <string>
namespace callback_impl {
struct callback_identification_type {};
template <typename T, typename = void>
struct is_callback : std::false_type {};
template <typename T>
struct is_callback<T,
std::enable_if_t<std::is_same<typename T::callback_id_type,
callback_identification_type>::value>>
: std::true_type {};
}
template <typename... FArgs>
struct Callback {
struct Handle {
using callback_id_type = callback_impl::callback_identification_type;
};
};
struct BaseCallbackHandle {
};
using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;
template <typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H handle) {
static_assert(callback_impl::is_callback<H>::value,
"The handle type is not a member of a recognised Callback<T...>");
return {};
}
int main() {
Callback<int>::Handle h;
std::string s;
makeTypeErasedCallbackHandle(h); //should compile fine
makeTypeErasedCallbackHandle(s); //should raise a compile error
return 0;
}
Live example
Output:
g++ -std=c++14 -O2 -Wall -Wno-unused-local-typedefs -pedantic -pthread main.cpp && ./a.out
main.cpp: In instantiation of 'TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H) [with H = std::__cxx11::basic_string<char>; TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>]':
main.cpp:41:35: required from here
main.cpp:32:5: error: static assertion failed: The handle type is not a member of a recognised Callback<T...>
static_assert(callback_impl::is_callback<H>::value,
^~~~~~~~~~~~~
One way to do it is by passing some extra arguments:
template <typename... Pack> struct packer {};
using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;
template <typename... T>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle(typename Callback<T...>::Handle h, T...)
{
return {};
}
template <typename... T>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle_2(typename Callback<T...>::Handle h, packer<T...>)
{
return {};
}
int main()
{
Callback<int>::Handle h;
std::string s;
makeTypeErasedCallbackHandle(h, 0); //should compile fine
// OR
makeTypeErasedCallbackHandle_2(h, packer<int>());
//makeTypeErasedCallbackHandle(s); //should raise a compile error
}
This makes use of identity trick
(by Stephan T. Lavavej) for doing the type deduction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With