Сочетание void_t и enable_if?

В C++17, void_t можно легко делать SFINAE с class/struct шаблонами:

template <class T, class = void>
struct test {
    static constexpr auto text = "general case";
};

template <class T>
struct test<T, std::void_t<decltype(std::begin(std::declval<T>())>> {
    static constexpr auto text = "has begin iterator";
};

То, что находится внутри void_t, является типом. Мой вопрос: как сделать то же самое, когда то, что внутри void_t, является чертой типа. Использование enable_if работает хорошо:

template <class T>
struct test<T, std::void_t<std::enable_if_t<std::is_class_v<T>>> {
    static constexpr auto text = "is a class";
};

Есть ли более короткий/элегантный способ написать это или правильный способ сделать это, действительно объединить void_t и enable_if?


person Vincent    schedule 01.03.2018    source источник


Ответы (2)


Важным моментом std::void_t является то, что он является вариативным.

// ................VVV  <- is variadic
template <typename ...>
using void_t = void;

поэтому позвольте SFINAE работать, когда вам нужно проверить множество типов, и разрешите вам программный отказ, когда только один из них не работает.

В случае, когда вам нужно проверить только значение и вы должны проверить его с помощью std::enable_if (или похожего типажа), я не вижу смысла использовать его вместе с std::void_t.

Итак, в вашем примере "правильный путь" (ИМХО) - избегать использования std::void_t

template <class T>
struct test<T, std::enable_if_t<std::is_class_v<T>>
 { static constexpr auto text = "is a class"; };

Также в случае использования одного decltype() я предпочитаю старый способ (но полагаю, это вопрос личного вкуса)

template <class T>
struct test<T, decltype(std::begin(std::declval<T>(), void())>
 { static constexpr auto text = "has begin iterator"; };
person max66    schedule 01.03.2018
comment
Нет, мы должны использовать эту потрясающую новую технику. На самом деле, это так здорово, что мы должны использовать его столько раз, сколько сможем: std::void_t<std::void_t<std::void_t<std::void_t<std::void_t<std::enable_if_t<...>>>>>> :) - person T.C.; 01.03.2018
comment
@Т.С. - вы имеете в виду, что я устарел со старым decltype() способом объявления целочисленного значения :( - decltype(decltype(decltype(decltype(decltype(std::enable_if_t<true>(), int{}){}){}){}){}) intVal; - person max66; 01.03.2018

Вы, кажется, не понимаете, зачем нужно std::void_t. Исправим это :)

В вашем первом примере, если вы не используете std::void_t, то частичная специализация никогда не будет выбрана, потому что decltype будет оценивать какой-то тип T, который не является void, и поэтому он не будет соответствовать частичной специализации и будет вернуться к общему случаю. Теперь вы всегда можете изменить аргумент по умолчанию в основном шаблоне, если знаете, что ваша функция всегда будет возвращать один и тот же тип, но этот способ более уязвим для изменения. (Вы можете взглянуть на ответ, который Я дал другой вопрос, который может помочь понять это).

Вот почему std::void_t был введен. std::void_t — это просто черта типа идентичности, которая, несмотря ни на что, равна void. Это означает, что в вашем первом примере, независимо от того, что оценивает decltype, второй параметр шаблона будет void и, таким образом, будет соответствовать, и специализация будет выбрана, если decltype правильно сформирован.

std::enable_if_t является действительным и по умолчанию является void тогда и только тогда, когда условие внутри него оценивается как истинное. Это означает, что std::enable_if_t уже возвращает void несмотря ни на что, и поэтому вам не нужно std::void_t, чтобы "преобразовать" его в void, потому что это уже void.

person Rakete1111    schedule 03.03.2018
comment
Если я объяснил что-то, что не имеет смысла, пожалуйста, скажите мне. Иногда я не могу объяснить что-то очень хорошо, спасибо :) - person Rakete1111; 03.03.2018