Подтвердить что ты не робот

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

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

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

Любые идеи?

EDIT: поскольку многие комментарии запрашивают более конкретную версию проблемы, я напишу ее здесь:

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

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

4b9b3361

Ответ 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;
};

Пример в реальном времени здесь.

EDIT:

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

Вторая перегрузка выбирается только тогда, когда первая выбрана из набора перегрузки. Это происходит, когда смена аргумента шаблона не выполняется и не является ошибкой (SFINAE).

Это забавное выражение в левой части оператора запятой внутри decltype, что и делает это. Он может быть плохо сформирован, когда

  • суб-выражение &U::foo плохо сформировано, что может случиться, когда

    • U не является типом класса, или
    • U::foo недоступен, или
    • нет U::foo
  • результирующий указатель элемента не может быть static_cast целевому типу

Обратите внимание, что поиск &U::foo не сбой, когда U::foo сам будет неоднозначным. Это гарантируется в определенном контексте, указанном в стандарте С++ в разделе 13.4 (Адрес перегруженной функции, [over.over]). Одним из таких контекстов является явное преобразование типов (static_cast в этом случае).

В выражении также используется тот факт, что T B::* преобразуется в T D::*, где D - это класс, полученный из B (но не наоборот). Таким образом, нет необходимости выводить тип класса, например, в ответ iavr.

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.

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

Ответ 2

Ниже приведено рабочее решение вашей проблемы, опубликованное в https://ideone.com/mxIVw3 - см. также живой пример.

Эта проблема в некотором смысле является продолжением Вывести родительский класс унаследованного метода в С++. В мой ответ я определил тип 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 или ref-qualifiers. Существует 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 'ed class 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 не работает в этом случае.

Ответ 3

Если вы просто хотите проверить наличие интерфейса на заданном типе 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

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