Я бы предложил альтернативный подход: 2 перегрузки (всегда предпочитают перегрузки специализациям шаблонов), которые вызывают общую функцию, которая содержит общий код:
namespace detail
{
template <class T>
auto foo_impl(const T& a)
{
// common code
}
}
template <class T>
auto foo(const std::array<T, 3>& a)
{
detail::foo_impl(a);
}
template <class T>
auto foo(const T(&a)[3])
{
detail::foo_impl(a);
}
Это понятно, без проблем и позволяет избежать повторения кода.
Альтернативой является создание собственной черты:
template <class T, std::size_t Size>
struct my_is_array : std::false_type
{};
template <class T, std::size_t Size>
struct my_is_array<std::array<T, Size>, Size> : std::true_type
{};
template <class T, std::size_t Size>
struct my_is_array<T[Size], Size> : std::true_type
{};
template<typename T>
std::enable_if_t<my_is_array<T, 3>::value> foo(const T& param) {}
или (на самом деле мне больше нравится этот):
template <class T>
struct array_size_or_zero : std::integral_constant<std::size_t, 0>
{};
template <class T, std::size_t Size>
struct array_size_or_zero<std::array<T, Size>> : std::integral_constant<std::size_t, Size>
{};
template <class T, std::size_t Size>
struct array_size_or_zero<T[Size]> : std::integral_constant<std::size_t, Size>
{};
template<typename T>
std::enable_if_t<array_size_or_zero<T>::value == 3> foo(const T& param) {}
Осторожно !!: foo
должен иметь параметр по ссылке, в противном случае массив распадается на указатель.