Is it possible in C++14 (so no constraints, require
or fold expressions, but there is SFINAE) to have a partial template specialization of a class template
template <typename ...T>
class Generic {
using Container = std::tuple<T...>;
};
for the case when every template parameter is the same type?
I need this because the generic version would use an std::tuple
container, while the specialization would use an std::array
(and not std::vector
, because the code will run in an embedded environment, so I'd like to avoid heap allocations).
For example Generic<int,float,char>::Container
should be std::tuple<int,float,char>
, while Generic<int,int,int>::Container
should be std::array<int,3>
.
It's not hard to do with C++14 only features. Since that standard relaxed a bunch of constraints on constant evaluation, we can even do it in a fairly readable way.
Just need a few helpers:
#include <type_traits>
#include <tuple>
#include <array>
namespace detail {
template<typename...>
struct head_impl;
template<typename H, typename ...Rest>
struct head_impl<H, Rest...> { using type = H; };
template<typename ...Ts>
using head = typename head_impl<Ts...>::type;
template<typename ...Ts>
inline constexpr bool test_same() {
using T = head<Ts...>;
for(bool b : { std::is_same<T, Ts>::value... }) {
if (!b)
return false;
}
return true;
}
template<>
inline constexpr bool test_same<>() { return true; }
}
template <typename ...T>
class Generic {
using Container = std::conditional_t<
detail::test_same<T...>(),
std::array<detail::head<T...>, sizeof...(T)>,
std::tuple<T...>
>;
};
The crux of the work is in the detail
namespace. We write the predicate as a constexpr
function, and rely on std::conditional_t
to choose a container type. Since in C++14 contexpr
functions aren't limited to a single return statement, we can just convert the type pack into an initializer_list<bool>
that we immediately iterate. Each item in the list is the result of checking if we have the same type in the pack in that position, as we have in the head.
Now, I didn't use specialization like you asked and that's primarily because it's tricky to do without rewriting the primary or adding delegation, and I assumed only the container matters. If that's not the case, you can still reuse the utilities and do something like this
template <bool, typename ...T>
class Generic_Base;
template <typename ...T>
class Generic_Base<false, T...> {
using Container = std::tuple<T...>;
};
template <typename ...T>
class Generic_Base<true, T...> {
using Container = std::array<detail::head<T...>, sizeof...(T)>;
};
template <typename ...T>
class Generic : public Generic_Base<detail::test_same<T...>(), T...> {
};
Specialize the bases as much as needed, and pull the correct base into Generic
(which can also have common members).