Функция шаблона для обнаружения типов указателей типа (с возможностью разграничения) не подходит для фактических типов указателей

51
3

Я пытаюсь написать механизм, чтобы определить, является ли тип указателем типа. Под этим я подразумеваю, что это происходит с помощью operator*() и operator->().

У меня есть три различные структуры, которые специализируются соответственно:

is_pointer_like_dereferencable который проверяет operator*() is_pointer_like_arrow_dereferencable который проверяет наличие operator->() is_pointer_like который просто объединяет 1 & 2

Я добавил специализации для не templated типов, таких как int, int*,... и для шаблонных типов, таких как std::vector<...>, std::shared_ptr<...>,...

Однако, похоже, я допустил ошибку при реализации is_pointer_like_arrow_dereferencable. Соответствующий код

template <typename T, typename = void>
struct is_pointer_like_arrow_dereferencable : std::false_type
{
};

template <typename T>
struct is_pointer_like_arrow_dereferencable<T, std::enable_if_t<
std::is_pointer_v<T> ||
std::is_same_v<decltype(std::declval<T>().operator->()), std::add_pointer_t<T>>>
> : std::true_type
{
};

template <template <typename...> typename P, typename T, typename... R>
struct is_pointer_like_arrow_dereferencable<P<T, R...>, std::enable_if_t<
std::is_same_v<decltype(std::declval<P<T, R...>>().operator->()), std::add_pointer_t<T>>>
> : std::true_type
{
};

template <typename T>
constexpr bool is_pointer_like_arrow_dereferencable_v = is_pointer_like_arrow_dereferencable<T>::value;

Вторая структура должна проверить, не является ли шаблон, не являющийся шаблоном, либо фактическим типом указателя, либо если тип имеет оператор стрелки, который возвращает указатель на себя. Но когда я тестирую механизм с приведенным ниже кодом, типы указателей (например, int* не обнаружены правильно). Это почему?

template <typename T>
struct Test
{
T& operator*()
{
return *this;
}

T* operator->()
{
return this;
}
};

void main()
{
bool
a = is_pointer_like_arrow_dereferencable_v<int>, // false
b = is_pointer_like_arrow_dereferencable_v<int*>, // false, should be true
c = is_pointer_like_arrow_dereferencable_v<vector<int>>, // false
d = is_pointer_like_arrow_dereferencable_v<vector<int>*>, // false, should be true
e = is_pointer_like_arrow_dereferencable_v<Test<int>>, // true
f = is_pointer_like_arrow_dereferencable_v<Test<int>*>, // false, should be true
g = is_pointer_like_arrow_dereferencable_v<shared_ptr<int>>, // true
h = is_pointer_like_arrow_dereferencable_v<shared_ptr<int>*>, // false, should be true
i = is_pointer_like_arrow_dereferencable_v<int***>; // false
}

Структура is_pointer_like_dereferencable отличается только от части std::is_same_v<...> и она правильно определяет фактические типы указателей.

Тот факт, что он не обнаруживает типы указателей (который должен быть покрыт std::is_pointer_v<...>), не имеет для меня никакого смысла. Может кто-нибудь объяснить это?

спросил(а) 2018-04-18T19:36:00+03:00 1 год, 11 месяцев назад
1
Решение
74

Но когда я тестирую механизм с приведенным ниже кодом, типы указателей (например, int * не обнаружены правильно). Это почему?

SFINAE: Ошибка замены не является ошибкой

Таким образом, для int* из decltype(std::declval<T>().operator->() вы получаете сбой замены и специализация не учитывается. Поэтому используется общая форма, поэтому std::false

Вы должны написать две специализации: один или указатель и один для operator->().

Ответ на бонус: вместо признаков типа как is_pointer_like_arrow_dereferencable (is_pointer_like_arrow_dereferencable, IMHO), я предлагаю вам пройти через набор вспомогательных функций (только объявленных)

template <typename>
std::false_type is_pointer_like (unsigned long);

template <typename T>
auto is_pointer_like (int)
-> decltype( * std::declval<T>(), std::true_type{} );

template <typename T>
auto is_pointer_like (long)
-> decltype( std::declval<T>().operator->(), std::true_type{} );

поэтому is_pointer_like_arrow_dereferencable можно просто написать как using

template <typename T>
using is_pointer_like_arrow_dereferencable = decltype(is_pointer_like<T>(0));

с помощником is_pointer_like_arrow_dereferencable_v

template <typename T>
static auto const is_pointer_like_arrow_dereferencable_v
= is_pointer_like_arrow_dereferencable<T>::value;

Ниже приведен полный рабочий пример

#include <type_traits>
#include <iostream>
#include <memory>
#include <vector>

template <typename>
std::false_type is_pointer_like (unsigned long);

template <typename T>
auto is_pointer_like (int)
-> decltype( * std::declval<T>(), std::true_type{} );

template <typename T>
auto is_pointer_like (long)
-> decltype( std::declval<T>().operator->(), std::true_type{} );

template <typename T>
using is_pointer_like_arrow_dereferencable = decltype(is_pointer_like<T>(0));

template <typename T>
static auto const is_pointer_like_arrow_dereferencable_v
= is_pointer_like_arrow_dereferencable<T>::value;

template <typename T>
struct Test
{
T & operator* () { return *this; }
T * operator-> () { return this; }
};

int main()
{
std::cout << is_pointer_like_arrow_dereferencable_v<int>
<< std::endl, // false
std::cout << is_pointer_like_arrow_dereferencable_v<int*>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<std::vector<int>>
<< std::endl, // false
std::cout << is_pointer_like_arrow_dereferencable_v<std::vector<int>*>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<Test<int>>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<Test<int>*>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<std::shared_ptr<int>>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<std::shared_ptr<int>*>
<< std::endl, // true
std::cout << is_pointer_like_arrow_dereferencable_v<int***>
<< std::endl; // true
}

ответил(а) 2018-04-18T19:41:00+03:00 1 год, 11 месяцев назад
62

Я предлагаю использовать std::experimental::is_detected а затем просто:

template <typename T> dereferencable_type = decltype(*declval<T>());
template <typename T> arrow_type = decltype(declval<T>().operator->());

template <typename T> is_dereferencable =
std::experimental::is_detected<dereferencable_type, T>;
template <typename T> has_arrow = std::experimental::is_detected<arrow_type, T>;

а затем составить эти черты:

template <typename T>
using is_pointer_like_dereferencable = is_dereferencable<T>;

template <typename T>
is_pointer_like_arrow_dereferencable = std::disjunction<std::is_pointer<T>, has_arrow<T>>;

template <typename T>
std::is_pointer_like = std::conjunction<is_pointer_like_dereferencable<T>,
is_pointer_like_arrow_dereferencable<T>>;

ответил(а) 2018-04-18T22:35:00+03:00 1 год, 11 месяцев назад
51

Во-первых, эта специализация в лучшем случае бессмысленна, но на самом деле неверна:

template <template <typename...> typename P, typename T, typename... R>
struct is_pointer_like_arrow_dereferencable<P<T, R...>,
std::enable_if_t<
std::is_same_v<
decltype(std::declval<P<T, R...>>().operator->()),
std::add_pointer_t<T>>>
> : std::true_type
{ };

Нет ничего особенного в специализациях шаблонов классов со всеми параметрами типа, когда речь идет о указателе. Поэтому вам не нужно рассматривать P<T, R...> иначе, чем просто T

Теперь эта специализация также имеет проблемы:

template <typename T>
struct is_pointer_like_arrow_dereferencable<T,
std::enable_if_t<
std::is_pointer_v<T> ||
std::is_same_v<decltype(std::declval<T>().operator->()), std::add_pointer_t<T>>>
> : std::true_type
{
};

Во-первых, необработанные указатели не имеют .operator->(). Это делает все булевое выражение плохо сформированным, что приводит к тому, что вся специализация будет выбрасываться. Короткое замыкание происходит в выражении по выражению, но каждое выражение все равно должно быть действительным.

Во-вторых, -> не нужно возвращать add_pointer_t<T>. std::vector<X>::iterator::operator-> возвращает X*, а не iterator*. Так что просто проверяйте неправильную вещь.

Мы можем дать возможность проверить базовый регистр для указателей и просто проверить специализацию .operator-> для всех типов (независимо от того, являются ли они специализациями шаблонов или нет):

template <typename T, typename = void>
struct is_pointer_like_arrow_dereferencable
: std::is_pointer<T>
{ };

template <typename T>
struct is_pointer_like_arrow_dereferencable<T,
void_t<decltype(std::declval<T>().operator->())>
>
: std::true_type
{ };

Тем не менее, is_pointer вероятно, недостаточно, поскольку для int* не имеет смысла быть "стрелкой, is_pointer ". Поэтому вы, вероятно, захотите сделать что-то вроде:

template <typename T, typename = void>
struct is_pointer_like_arrow_dereferencable
: std::false_type
{ };

template <typename T>
struct is_pointer_like_arrow_dereferencable<T*, void>
: std::disjunction<std::is_class<T>, std::is_union<T>>
{ };

Или некоторые такие.

ответил(а) 2018-04-18T20:14:00+03:00 1 год, 11 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема