Тип соответствия унаследованных функций-членов

У меня есть следующий фрагмент кода, который не компилируется.

#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 отклоняет его, и я понятия не имею, кто прав.

Есть идеи?

РЕДАКТИРОВАТЬ: так как многие комментарии просят более конкретную версию проблемы, я напишу это здесь:

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

Таким образом, мне нужно иметь возможность проверять тип возвращаемого значения, тип и число аргумента, константность и т. Д. Каждой функции-члена, которую я запрашиваю. Первоначальный вопрос был проверкой части большого шаблона, который я использую для проверки совпадений.

13

Решение

Вот простой класс, который проходит ваши тесты (и не требует дюжины специализаций :)). Это также работает, когда 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 это делает это возможным. Это может быть плохо сформировано, когда

  1. подвыражение &U::foo плохо сформирован, что может произойти, когда

    • U это не тип класса, или
    • U::foo недопустимо, или
    • здесь нет U::foo
  2. результирующий указатель на член не может быть 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,

Надеюсь, это поможет.

3

Другие решения

Рабочее решение вашей проблемы, опубликованное на 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 не работает в этом случае.

6

Если вы просто хотите проверить существование интерфейса для данного типа 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

Надеюсь, это поможет.

4

Я предлагаю с помощью decltype для общего определения типа указателей на функции-члены:

helper<decltype(&A::foo), &A::foo> compiles;
helper<decltype(&B::foo), &B::foo> also_compiles;

Это может показаться DRY нарушение, но повторение имени принципиально не хуже, чем указание типа отдельно от имени.

0
По вопросам рекламы [email protected]