I'm making a struct Box<T>
that handles some data. The specifics are unimportant.
An important note however is that Box<T>
can store a pointer, but it might not. So both Box<int>
and Box<int *>
are valid. Obviously, if we own Box.data
, we're going to need to delete
data if it is a pointer type.
Here's a solution I came up with that works in C 11:
template <typename T> struct BoxTraits;
template <typename T> struct Box {
using traits_t = BoxTraits<T>;
T data;
~Box() = default; // not required, I know
T get_data() { return traits_t::get_data(this); }
};
template <typename T> struct Box<T *> {
using traits_t = BoxTraits<T *>;
T *data;
~Box() { delete data; }
T *get_data() { return traits_t::get_data(this); }
};
template <typename T> struct BoxTraits {
static T get_data(Box<T> *const box) { return box->data; }
};
Box::get_data
is here to illustrate an issue with this design pattern. For every single method I want to add to Box
, I need to add some boiler plate in each specialisation. Note that I would also need a Box<T *const>
specialisation.
This seems like quite a rubbish solution. In C 14, I could use if constexpr
with a is_ptr<T>
trait and only have to write extra code in the methods that need specialising... Is there any way I can do this is in C 11?
This solution is shorter, cleaner and works for Box<U *const>
!
template <typename T> struct is_ptr { static const bool value = false; };
template <typename U> struct is_ptr<U *> { static const bool value = true; };
template <typename U> struct is_ptr<U *const> {
static const bool value = true;
};
template <typename T> struct Box {
T data;
~Box() {
if constexpr (is_ptr<T>::value) {
delete data;
}
}
T get_data() { return data; }
};
CodePudding user response:
First off, C 11 already has std::is_pointer, no need to roll your own. You can see that it inherits from std::true_type
or std::false_type
instead of defining its own value
member. The reason for that is tag dispatching, that can effectively replace if constexpr
in this situation:
template <typename T> struct Box {
T data;
~Box() {
destroy(std::is_pointer<T>{});
}
private:
void destroy(std::true_type) {
delete data;
}
void destroy(std::false_type) {} // nothing to do
};
I think this is the most idiomatic way in C 11 for delegating to different implementations based on type traits. In many situations, tag dispatching can replace if constexpr
(from C 17, not C 14), and I believe the latter always replaces the former in addition to being clearer. Tag dispatching can also be used before C 11 if you roll your own type traits.
Last note: you don't need to use the standard type traits, you can do something like this:
template <typename T> struct is_ptr { static const bool value = false; };
template <typename T> struct is_ptr<T*> { static const bool value = true; };
template <typename T> struct is_ptr<T* const> { static const bool value = true; };
template <typename T> struct is_ptr<T* volatile> { static const bool value = true; };
template <typename T> struct is_ptr<T* const volatile> { static const bool value = true; };
template<bool b>
struct bool_constant {};
template<typename T>
struct Box {
T data;
~Box() {
destroy(bool_constant<is_ptr<T>::value>{});
}
private:
void destroy(bool_constant<true>) {
delete data;
}
void destroy(bool_constant<false>) {} // nothing to do
};
However, this pretty much amounts to recreating the standard type traits, but probably worse. Just use the standard library when possible.
CodePudding user response:
I think you had the right idea with the helper type, but I'd do it like the following example illustrates.
template <typename B, typename T>
struct BoxTraits {
static T& get_data(B *const box) { return box->data; }
// ^--- important
static T const& get_data(B const* const box) { return box->data; }
};
template <typename T>
struct BoxTraits<Box<T*>, T> {
static T& get_data(Box<T*>* const box) { return *box->data; }
static T const& get_data(Box<T*> const* const box) { return *box->data; }
};
Both versions always return T
, so you can use them the same regardless of your Box's payload. You could add a type alias in Box so you don't have to pass the template arguments:
typedef Traits BoxTraits<Box, T>; // in Box class