Проверьте наличие оператора () - PullRequest
3 голосов
/ 22 июня 2019

Мне нужна черта типа HasCall, чтобы проверить правильность создания следующего экземпляра типа для T:

template<class T> struct Caller: T
{
    using T::operator();
};

Есть ли способ сделать это в C ++ 14? Вот моя попытка, но она не работает: https://godbolt.org/z/vxgJCR

EDIT

Я знаю о СФИНАЕ и как оно работает. Эта проблема сложнее, чем просто проверка правильности какого-либо выражения. Я хочу, чтобы эти утверждения прошли:

struct A {void operator()(int) {}};
static_assert(HasCall<A>, "Check must work with any set of arguments.");

struct B {void operator()() {}};
static_assert(HasCall<B>, "Check must work with any set of arguments.");

struct C {template<typename... Args> void operator()(Args&&...) {}};
static_assert(HasCall<C>, "Templated operators must be detected correctly.");

struct D {};
static_assert(!HasCall<D>, "No operator() at all.");

static_assert(!HasCall<void(*)()>, "Class cannot inherit from function pointers.");

Проверка правильности выражения & T :: operator () недостаточна, поскольку он не работает с перегруженным или шаблонным оператором ().

Пожалуйста, проверьте ваши решения с этими утверждениями.

Этот вопрос не является дубликатом.

Ответы [ 2 ]

4 голосов
/ 22 июня 2019

Нет, нет способа сделать это.

Все решения требуют знания точной или совместимой подписи вызова или отсутствия перегрузок.

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

Вам придется найти другой способ решения проблемы или дождаться размышлений.

4 голосов
/ 22 июня 2019

Попробуйте это:

template <typename T, typename = void> struct has_operator {
    enum { value = 0 };
};
// thanks super for great suggestion!
template <typename T> struct has_operator<T, std::void_t<decltype(std::declval<T>()())>> {
    enum { value = 1 };
};
template<class T, typename = std::enable_if<has_operator<T>::value>> struct Caller: T
{
    using T::operator();
};

Это работает по принципу SFINAE - некоторые ошибки в реализации шаблона вместо сбоя всей компиляции просто заставят компилятор игнорировать данную реализацию. Итак, сначала мы определяем has_operator с value = 0, что является нашим «значением по умолчанию». Затем мы делаем специализацию шаблона для типа T. Теперь мы хотим, чтобы эта специализация выбиралась только тогда, когда существует T::operator(). Поэтому мы добавляем второй аргумент шаблона и устанавливаем его значение по умолчанию decltype(&T::operator()) и value = 1. Мы не заботимся о реальном типе здесь, мы заботимся о том, что если существует T::operator(), это скомпилируется просто отлично. И компилятор выберет эту специализацию для этого T. Когда он не существует - компилятор проигнорирует эту специализацию и выберет "по умолчанию" has_operator, который имеет value = 0.

Так что теперь у нас есть struct has_operator, который - при использовании так: has_operator<T>::value будет выдавать постоянное значение 0, когда T не имеет operator () (с любыми аргументами, обратите внимание) и значение 1, когда имеет , Вы можете использовать его с std::enable_if (что, кстати, работает примерно так же).

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

...