У меня есть следующий фрагмент кода, который не компилируется.
#include <iostream>
struct A {
void foo() {}
};
struct B : public A {
using A::foo;
};
template<typename U, U> struct helper{};
int main() {
helper<void (A::*)(), &A::foo> compiles;
helper<void (B::*)(), &B::foo> does_not_compile;
return 0;
}
Не компилируется, т.к. &B::foo
решает в &A::foo
и, следовательно, он не может соответствовать предложенному типу void (B::*)()
, Так как это часть шаблона SFINAE, который я использую для проверки очень специфического интерфейса (я заставляю определенные типы аргументов и типы вывода), я хотел бы, чтобы это работало независимо от наследования, сохраняя читаемость проверки.
То, что я пробовал, включает в себя:
Приводим вторую часть аргумента:
helper<void (B::*)(), (void (B::*)())&B::foo> does_not_compile;
Это, к сожалению, не помогает, так как вторая часть теперь не распознается как константное выражение и завершается ошибкой.
Я попытался присвоить ссылку на переменную, чтобы проверить это.
constexpr void (B::* p)() = &B::foo;
helper<void (B::* const)(), p> half_compiles;
Этот код принят clang 3.4, но g ++ 4.8.1 отклоняет его, и я понятия не имею, кто прав.
Есть идеи?
РЕДАКТИРОВАТЬ: так как многие комментарии просят более конкретную версию проблемы, я напишу это здесь:
То, что я ищу, это способ явно проверить, что класс уважает определенный интерфейс. Эта проверка будет использоваться для проверки входных аргументов в шаблонных функциях, чтобы они соблюдали контракт, требуемый этими функциями, так что компиляция заранее останавливается в случае несовместимости класса и функции (то есть проверка типа типа проверки).
Таким образом, мне нужно иметь возможность проверять тип возвращаемого значения, тип и число аргумента, константность и т. Д. Каждой функции-члена, которую я запрашиваю. Первоначальный вопрос был проверкой части большого шаблона, который я использую для проверки совпадений.
Вот простой класс, который проходит ваши тесты (и не требует дюжины специализаций :)). Это также работает, когда foo
перегружен Подпись, которую вы хотите проверить, также может быть параметром шаблона (это хорошо, верно?).
#include <type_traits>
template <typename T>
struct is_foo {
template<typename U>
static auto check(int) ->
decltype( static_cast< void (U::*)() const >(&U::foo), std::true_type() );
// ^^^^^^^^^^^^^^^^^^^
// the desired signature goes here
template<typename>
static std::false_type check(...);
static constexpr bool value = decltype(check<T>(0))::value;
};
Живой пример Вот.
РЕДАКТИРОВАТЬ :
У нас есть две перегрузки check
, Оба могут принимать целочисленный литерал в качестве параметра, и поскольку второй имеет многоточие в списке параметров, он никогда не будет наилучшим жизнеспособным в разрешении перегрузки, когда обе перегрузки жизнеспособны (elipsis-преобразования последовательности хуже, чем любая другая последовательность преобразования). Это позволяет нам однозначно инициализировать value
член класса черты позже.
Вторая перегрузка выбирается только тогда, когда первая исключается из набора перегрузки. Это происходит, когда подстановка аргумента шаблона завершается неудачно и не является ошибкой (SFINAE).
Это забавное выражение слева от оператора запятой внутри decltype
это делает это возможным. Это может быть плохо сформировано, когда
подвыражение &U::foo
плохо сформирован, что может произойти, когда
U
это не тип класса, илиU::foo
недопустимо, илиU::foo
результирующий указатель на член не может быть static_cast
к целевому типу
Обратите внимание, что глядя вверх &U::foo
не терпит неудачу, когда U::foo
само по себе было бы неоднозначно. Это гарантировано в определенном контексте, указанном в стандарте C ++ под 13.4
(Адрес перегруженной функции, [over.over]). Одним из таких контекстов является явное преобразование типов (static_cast
в этом случае).
Выражение также использует тот факт, что T B::*
конвертируется в T D::*
где D
это класс, полученный из B
(но не наоборот). Таким образом, нет необходимости выводить тип класса, как в ответ иавра.
value
элемент затем инициализируется с value
либо true_type
или же false_type
,
Однако есть потенциальная проблема с этим решением. Рассматривать:
struct X {
void foo() const;
};
struct Y : X {
int foo(); // hides X::foo
};
Сейчас is_foo<Y>::value
дам false
, потому что имя поиска для foo
остановится при встрече Y::foo
, Если это нежелательное поведение, рассмотрите возможность передачи класса, в котором вы хотите выполнить поиск, в качестве параметра шаблона is_foo
и использовать его вместо &U::foo
,
Надеюсь, это поможет.
Рабочее решение вашей проблемы, опубликованное на https://ideone.com/mxIVw3 дается ниже — см. также живой пример.
Эта проблема в некотором смысле является продолжением Вывести родительский класс унаследованного метода в C ++. В мой ответ, Я определил черту типа member_class
который извлекает класс из заданного указателя на тип функции-члена. Ниже мы используем еще несколько черт для анализа, а затем синтезируем обратно такой тип.
Первый, member_type
извлекает подпись, например void (C::*)()
дает void()
:
template <typename M> struct member_type_t { };
template <typename M> using member_type = typename member_type_t <M>::type;
template <typename T, typename C>
struct member_type_t <T C::*> { using type = T;};
Затем, member_class
извлекает класс, например void (C::*)()
дает C
:
template<typename>
struct member_class_t;
template<typename M>
using member_class = typename member_class_t <M>::type;
template<typename R, typename C, typename... A>
struct member_class_t <R(C::*)(A...)> { using type = C; };
template<typename R, typename C, typename... A>
struct member_class_t <R(C::*)(A...) const> { using type = C const; };
// ...other qualifier specializations
В заключение, member_ptr
синтезирует указатель на тип функции-члена с учетом класса и сигнатуры, например, C
+ void()
дать void (C::*)()
:
template <typename C, typename S>
struct member_ptr_t;
template <typename C, typename S>
using member_ptr = typename member_ptr_t <C, S>::type;
template <typename C, typename R, typename ...A>
struct member_ptr_t <C, R(A...)> { using type = R (C::*)(A...); };
template <typename C, typename R, typename ...A>
struct member_ptr_t <C const, R(A...)> { using type = R (C::*)(A...) const; };
// ...other qualifier specializations
Два предыдущих признака нуждаются в большей специализации для того, чтобы разные классификаторы были более общими, например const/volatile
или реф-квалификаторы. Есть 12 комбинаций (или 13, включая элементы данных); полная реализация Вот.
Идея состоит в том, что любые классификаторы передаются member_class
от указателя на тип функции-члена до самого класса. затем member_ptr
переводит классификаторы из класса обратно в тип указателя. Пока квалификаторы относятся к типу класса, можно свободно манипулировать стандартными чертами, например, добавить или удалить const
, lvalue / rvalue ссылки и т. д.
Теперь вот ваш is_foo
тестовое задание:
template <typename T>
struct is_foo {
private:
template<
typename Z,
typename M = decltype(&Z::foo),
typename C = typename std::decay<member_class<M>>::type,
typename S = member_type<M>
>
using pattern = member_ptr<C const, void()>;
template<typename U, U> struct helper{};
template <typename Z> static auto test(Z z) -> decltype(
helper<pattern<Z>, &Z::foo>(),
// All other requirements follow..
std::true_type()
);
template <typename> static auto test(...) -> std::false_type;
public:
enum { value = std::is_same<decltype(test<T>(std::declval<T>())),std::true_type>::value };
};
Данный тип Z
псевдоним шаблона pattern
получает правильный тип M
указателя члена с decltype(&Z::foo)
извлекает его decay
‘класс C
и подпись S
и синтезирует новый тип указатель на член-функцию с классом C const
и подпись void()
т.е. void (C::*)() const
, Это именно то, что вам нужно: то же самое с вашим оригинальным жестко закодированным шаблоном, с типом Z
заменен на правильный класс C
(возможно, базовый класс), как найдено decltype
,
Графически:
M = void (Z::*)() const -> Z + void()
-> Z const + void()
-> void (Z::*)() const == M
-> SUCCESS
M = int (Z::*)() const& -> Z const& + int()
-> Z const + void()
-> void (Z::*)() const != M
-> FAILURE
На самом деле, подпись S
здесь не было необходимости, поэтому не было member_type
, Но я использовал это в процессе, поэтому я включил это здесь для полноты. Это может быть полезно в более общих случаях.
Конечно, все это не будет работать для нескольких перегрузок, потому что decltype
не работает в этом случае.
Если вы просто хотите проверить существование интерфейса для данного типа T, то есть лучшие способы сделать это. Вот один пример:
template<typename T>
struct has_foo
{
template<typename U>
constexpr static auto sfinae(U *obj) -> decltype(obj->foo(), bool()) { return true; }
constexpr static auto sfinae(...) -> bool { return false; }
constexpr static bool value = sfinae(static_cast<T*>(0));
};
Тестовый код:
struct A {
void foo() {}
};
struct B : public A {
using A::foo;
};
struct C{};
int main()
{
std::cout << has_foo<A>::value << std::endl;
std::cout << has_foo<B>::value << std::endl;
std::cout << has_foo<C>::value << std::endl;
std::cout << has_foo<int>::value << std::endl;
return 0;
}
Выход (демонстрация):
1
1
0
0
Надеюсь, это поможет.
Я предлагаю с помощью decltype
для общего определения типа указателей на функции-члены:
helper<decltype(&A::foo), &A::foo> compiles;
helper<decltype(&B::foo), &B::foo> also_compiles;
Это может показаться DRY нарушение, но повторение имени принципиально не хуже, чем указание типа отдельно от имени.