Я спросил вопрос это имеет несколько ссылок на код:
template <typename...>
using void_t = void;
Я считаю, что я вообще неправильно понимаю шаблоны псевдонимов:
Почему бы вам просто не оценить какой-либо параметр шаблона, который вы передаете в шаблон псевдонима в enable_if_t
или же conditional_t
заявление?
Код выше только о выполнении enable_if_t
на несколько параметров шаблона одновременно?
Во-вторых, я считаю, что у меня есть конкретное недопонимание роли void_t
, Этот комментарий утверждает, что стандарт C ++ 17 определяет void_t
, Вот что я не получаю:
не void_t
просто произвольное имя? Если я все еще должен определить template <typename...> using void_t = void;
везде, где я планирую использовать void_t
какой смысл стандартизировать произвольное имя?
Я не думаю, что показанный пример действительно показывает, что void_t
хорошо, так как он показывает только один вариант использования, но когда вы смотрите на
template<typename T>
struct has_to_string<T,
void_t<decltype(std::to_string(std::declval<T>()))>
>
: std::true_type { };
это не так сильно отличается от
template<typename T>
struct has_to_string<T,
decltype(std::to_string(std::declval<T>()), void())
>
: std::true_type { };
И для этого утверждения:
Предыдущая версия намного легче читать и
void_t
не требуетdecltype
работать.
Я думаю, что преимущество в читаемости довольно мало, и вторая часть не имеет смысла, когда decltype
не работает, SFINAE запускает, как ожидалось.
Один пример, где void_t
Более полезным является тот из предложения:
// primary template handles types that have no nested ::type member
template< class, class = void_t<> >
struct has_type_member
: std::false_type { };
// specialization recognizes types that do have a nested ::type member
template< class T >
struct has_type_member<T, void_t<typename T::type>>
: std::true_type { }
Как видите, даже основной шаблон использует void_t
повысить удобочитаемость, так как теперь она соответствует специализации. Это не обязательно, но мне это нравится. Настоящая сила приходит, когда вы думаете об альтернативах. Без void_t
, специализация теперь сложнее:
template< class T >
struct has_type_member<T, decltype(typename T::type, void())>
: std::true_type { }
не будет работать как T::type
называет тип, а не выражение. Поэтому вам нужно
template< class T >
struct has_type_member<T, decltype(std::declval<typename T::type>(), void())>
: std::true_type { }
Все выражение становится длиннее, хитрее и может пострадать от крайних случаев, которые вы забыли обработать. Это где void_t
действительно помогает, другие применения — это лишь небольшое улучшение, и они увеличивают согласованность.
В примере Барри из вашего связанного вопроса:
template<typename T, typename = void>
struct has_to_string
: std::false_type { };
template<typename T>
struct has_to_string<T,
void_t<decltype(std::to_string(std::declval<T>()))>
>
: std::true_type { };
void_t
просто используется для перевода типа, выведенного decltype
в void
так что он соответствует аргумент по умолчанию для первичный определение шаблона. О СФИНАЕ все заботятся decltype
выражение. Вы можете так же легко сделать следующее:
//use , void() instead of wrapping in void_t
//this uses the comma operator to check the type of the to_string call, then change the type to void
decltype(std::to_string(std::declval<T>()), void())
Предыдущая версия намного легче читать и void_t
не требует decltype
работать.
Если void_t
доступен в вашей реализации, вам не нужно переопределять его. Когда он будет стандартизирован, он будет доступен, как и любой другой шаблон псевдонима в стандарте.
Подумайте об этом так: если T
является int
, который имеет действительный std::to_string
перегрузка, удержание будет выглядеть так:
has_to_string<int>
-> has_to_string<int,void>
из-за аргумента по умолчанию. Итак, давайте посмотрим на специализации has_to_string
с этими аргументами.
template<typename T>
struct has_to_string<T,
void_t<decltype(std::to_string(std::declval<T>()))>
>
: std::true_type { };
Хорошо, это частичная специализация для некоторых T
и некоторый зависимый тип. Давайте разработаем этот тип:
void_t<decltype(std::to_string(std::declval<T>()))>
//std::to_string(int&&) is valid and returns a std::string
void_t<std::string>
//void_t changes types to void
void
Теперь наша специализация выглядит так:
template<>
struct has_to_string<int,void>
: std::true_type { };
Это соответствует нашему воплощению has_string<int,void>
, так has_to_string<int>
наследуется от std::true_type
,
Теперь подумай, когда T
является struct Foo{};
, Опять же, давайте разработаем этот зависимый тип:
void_t<decltype(std::to_string(std::declval<T>()))>
//wait, std::to_string(Foo&&) doesn't exist
//discard that specialization
С этой специализацией мы отказались от основного шаблона:
template<typename T, typename = void>
struct has_to_string
: std::false_type { };
Так has_to_string<Foo>
наследуется от std::false_type
,