Операторы короткого замыкания в enable_if

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

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

Можно ли вызвать короткое замыкание до оценки условий?


person Jonathan Mee    schedule 29.08.2018    source источник
comment
Опечатки: extent_v и const T& param. Конечно, вы можете использовать специализацию, чтобы определить трейт для управления SFINAE, что позволяет избежать повторения тела функции.   -  person aschepler    schedule 29.08.2018
comment
@aschepler Хороший призыв к опечаткам. Я просто напечатал это по памяти, а не копировал :( Всегда плохой план. Возможно, ваш комментарий о характере - это то, что я ищу. Не могли бы вы уточнить?   -  person Jonathan Mee    schedule 29.08.2018
comment
Части my_is_array или array_size_or_zero из ответа Болова - это то, что я имел в виду под определением черты.   -  person aschepler    schedule 30.08.2018


Ответы (3)


Используя тот факт, что 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) {}
person T.C.    schedule 29.08.2018
comment
@aschepler, если я проголосую за ваш комментарий, можем ли мы считать это дополнительным голосом за TC? - person bolov; 29.08.2018
comment
Мне пришлось прочитать раздел "Примечания" disjunction, чтобы понять это. Но оказывается, что disjunction будет продолжать оценивать последующие аргументы, пока не найдет тот, который не приводится к 0. И что более важно, когда он находит аргумент, который не приводится к 0, он прекращает оценку аргументов. - person Jonathan Mee; 29.08.2018
comment
Ничего себе, тогда помогите мне понять здесь, tuple_size<int[3]> недействителен ... почему компилятору все равно, если я не использую _v? - person Jonathan Mee; 29.08.2018
comment
@JonathanMee Как отмечено в ответе, это код, который вы просили, но, вероятно, не тот код, который вы должны написать. Ответы на написание собственных метафункций на несколько порядков легче понять в окончательном коде. - person Max Langhof; 29.08.2018
comment
@JonathanMee Просто назвать tuple_size<int[3]> вполне допустимо. Что недопустимо, так это запрашивать члена value. - person T.C.; 29.08.2018
comment
@MaxLanghof Хммм... я что-то пропустил? Какие преимущества имеет здесь черта над conditional или disjunction? - person Jonathan Mee; 29.08.2018
comment
@JonathanMee Вам не нужно читать раздел заметок на справочном сайте, чтобы понять, как и почему это работает. Кроме того, дать условие, которое вы хотите использовать, так же хорошо (или даже лучше), как найти некоторую краткую комбинацию библиотечных инструментов для выражения того же самого. Тем не менее, вы можете комбинировать их: template<class T> inline constexpr bool isArray3 = (conditional_t<is_array_v<T>, extent<T>, tuple_size<T>>::value == 3U); - person Max Langhof; 29.08.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 }; };
person Karl    schedule 29.08.2018
comment
Проще наследовать std::false_type или std::true_type, плюс это соответствует тому, что делают все трейты библиотеки. - person aschepler; 29.08.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 должен иметь параметр по ссылке, иначе массив распадается на указатель.

person bolov    schedule 29.08.2018