Операторы короткого замыкания в enable_if - PullRequest
0 голосов
/ 29 августа 2018

Я хочу написать шаблонную функцию, которая принимает либо array<int, 3>, либо int[3]. Я пытаюсь запечатлеть это в enable_if:

template<typename T>
enable_if_t<is_array_v<T> && extent_v<T> == 3U || !is_array_v<T> && tuple_size<T>::value == 3U> foo(const T& param) {}

К сожалению, для int[3], tupple_size не определено, что приводит к сбою компиляции шаблона до оценки короткого замыкания.

Я также пытался сделать это, используя conditional, но у него та же проблема обеспечения того, что оба параметра действительны для T перед рассмотрением условия.

Я знаю, что могу сделать это по специализации. Но код точно такой же в теле функции. Я ненавижу тот факт, что я специализируюсь, когда реализация одинакова.

Есть ли способ, которым я могу вызвать короткое замыкание перед оценкой условий?

Ответы [ 3 ]

0 голосов
/ 29 августа 2018

Короче говоря, замены шаблонов всегда должны быть действительными. Вероятно, было бы проще просто определить конкретный шаблон для соответствия массивам:

template <typename T>
struct IsArrayInt3 { enum: bool { value = false }; };

template <>
struct IsArrayInt3<int[3]> { enum: bool { value = true }; };

template <>
struct IsArrayInt3<std::array<int, 3>> { enum: bool { value = true }; };
0 голосов
/ 29 августа 2018

Использование преимущества того факта, что extent<T> для типов, не являющихся массивами, равно нулю и, следовательно, ложно, и disjunction происходит от первого истинного типа в списке с коротким замыканием:

template<typename T>
enable_if_t<disjunction<extent<T>, tuple_size<T>>::value == 3U> foo(const T& param) {}

Возможно, это слишком умно. Обратите внимание, что вы не можете использовать disjunction_v здесь.


conditional тоже должно работать нормально. Хитрость заключается в том, чтобы не запрашивать ::value, пока вы не выбрали правильный тип:

template<typename T>
enable_if_t<conditional_t<is_array_v<T>, extent<T>, tuple_size<T>>::value == 3U> 
    foo(const T& param) {}
0 голосов
/ 29 августа 2018

Я бы предложил альтернативный подход: 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 должен иметь параметр по ссылке, в противном случае массив распадается на указатель.

...