#c #templates #c 14
#c #шаблоны #c 14
Вопрос:
Я хочу написать функцию, которая выводит значение по-разному для типов итераторов и других типов. После проведения некоторых исследований, вот что я придумал:
#include <iostream>
#include <iterator>
#include <experimental/type_traits>
namespace details
{
template <class T, class = void>
struct is_iterator : std::false_type
{
};
template <class T>
struct is_iterator<T, std::experimental::void_t<typename std::iterator_traits<T>::value_type>> : std::true_type
{
};
template <class T>
constexpr auto is_iterator_v = is_iterator<T>::value;
}
template <class T>
std::enable_if_t<!details::is_iterator_v<T>> print(const T amp;value)
{
std::cout << value;
}
template <class T>
std::enable_if_t<details::is_iterator_v<T>> print(const T amp;value)
{
std::cout << "(IteratorTo ";
print(*value);
std::cout << ')';
}
Но я думаю, что мой код слишком длинный и немного сложный. Возможно ли сделать мой код короче и чище?
Ответ №1:
SFINAE твой друг здесь:
template<class T>
constexpr std::true_type is_iterator(typename std::iterator_traits<T>::value_type*, int) {
return {};
}
template<class T>
constexpr std::false_type is_iterator(void*, long) {
return {};
}
template<class T>
constexpr auto is_iterator_v = is_iterator<T>(nullptr, 0);
Кроме того, альтернативная реализация print
с использованием диспетчеризации тегов, которую вы можете найти или не найти более читаемой:
template<class T>
void print(const T amp;);
namespace details {
template<class T>
void print(const T amp;value, std::false_type) {
std::cout << value;
}
template<class T>
void print(const T amp;value, std::true_type) {
std::cout << "(IteratorTo ";
print(*value);
std::cout << ')';
}
}
template<class T>
void print(const T amp;value) {
details::print(value, details::is_iterator_v<T>);
}
Комментарии:
1. @aschepler : Справедливые баллы всем! Бесстыдная лень с моей стороны. >_>
2. @ildjarn — хорошее решение ( 1); никогда не думал, что
int
/long
трюк для решения проблемы неоднозначности, когдаT
является итератором; но как насчет подписи последнегоprint()
в последнем примере? Не должно бытьvoid print (const T amp;value)
безstd::true_type
?3. @max66 : Действительно, ошибка неаккуратного копирования / вставки с моей стороны. Спасибо
4. Поддержано, я твердо считаю, что диспетчеризация тегов должна быть предпочтительным решением здесь. Длина кода не так важна, как тот факт, что пользовательский интерфейс является чистым и содержится в сигнатуре одной функции.
5. @ildjarn — извините, не видел раньше, но … в
print(const Tamp;)
, не должно бытьdetails::print(value, details::is_iterator_v<T>);
вместоdetails::print(T, details::is_iterator_v<T>);
(я имею в виду:value
вместоT
)? И еще один вопрос: есть причина, потому что вstd::false_type
версииis_iterator()
тип первого аргумента —void const volatile *
а неvoid const *
или простоvoid *
?
Ответ №2:
Я нахожу ваше решение простым, коротким и чистым.
Я вижу только проблему: вы отметили свой вопрос C 14, но вы предлагаете код, который использует std::experimental::void_t
то есть… ну … экспериментальный и никогда не доступный.
Но (если я не ошибаюсь), вы могли бы получить тот же результат и без void_t
, используя старый трюк с запятой decltype.
Вы можете написать свою std::true_type
версию следующим образом (изменена в попытке исправить проблему, на которую указал Ильджарн (спасибо!)):
template <typename T>
struct is_iterator<T,
decltype(std::declval<typename std::iterator_traits<T>::value_type>(),
void())> : std::true_type
{ };
Это решение также должно работать с C 11.
Комментарии:
1. Для этого требуется, чтобы
value_type
можно было создавать по умолчанию; довольно строгое требование!2. @ildjarn — D’oh! Вы правы. Слишком строгое требование. Поправьте меня, если я ошибаюсь:
std::declval
предполагается, что это решит эту проблему, не так ли? (смотрите Мой измененный ответ)