Почему эта С++-характеристика обнаруживает, что тип T имеет оператор void (EDT const &)?

81
10

Я пытаюсь использовать SFINAE для определения того, был ли тип, переданный как аргумент шаблона T, иметь T:: operator() (P const &), где P также является аргументом шаблона. Я смоделировал свое решение после этого примера Idoom Member Detector. К сожалению, я не мог заставить это работать для оператора(), хотя я мог заставить его функционировать для нормального метода.


Вот пример кода, который демонстрирует проблему, с которой я сталкиваюсь:


#include <iostream>
#include <iomanip>
#include <utility>
#include <type_traits>

using namespace std;

struct has
{
void operator()(int const&);
};

struct hasNot1
{
void operator()(int);
};

struct hasNot2
{
void operator()();
};

struct hasNot3
{
void operator()(float);
};

struct hasNot4
{
};

template<typename T, typename EDT>
struct is_callable_oper
{
private:
typedef char(&yes)[1];
typedef char(&no)[2];

template <typename U, void (U::*)(EDT const &)> struct
Check;
template<typename>
static yes test(...);

template <typename U>
static no
test(Check<U, &U::operator()>*);

public:
static constexpr bool value = sizeof(test<T>(0))
== sizeof(yes);
};

int main() {
cout << boolalpha << is_callable_oper<has, int&>::value << " "
<< is_callable_oper<has, int>::value << " "
<< is_callable_oper<hasNot1, int&>::value << " "
<< is_callable_oper<hasNot2, int&>::value << " "
<< is_callable_oper<hasNot3, int&>::value << " "
<< is_callable_oper<hasNot4, int&>::value << endl;
return 0;
}


Запуск на идеон (https://ideone.com/tE49xR):
true false true true true true


Я ожидал:
true false false false false false


Выполненная работа:


    Прочитайте этот qaru.site/questions/217846/.... Я также следил за связанными ссылками.


    Посмотрел std:: declval, decltype. Я также немного изучил, как параметр шаблона непигового типа приводит к двусмысленности. Я использовал http://en.cppreference.com/ в первую очередь.


    Прочитайте некоторые другие связанные вопросы и ссылки.


Примечание: Я работаю над gcc 4.6.3 с С++ 0x.


Конечная цель: Обнаружить все указатели на функции вызовов, включенные в эту подпись.


Связанные разъяснения: Я все еще смущен некоторыми понятиями, и был бы признателен, если бы вы могли ответить на них. Конечно, если они принадлежат к отдельному вопросу, пожалуйста, дайте мне знать.


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


    Каков тип функции для перегруженного оператора? Например, перегруженный оператор в этом случае имеет следующий тип: void (operator())(EDT const&)?


    Поскольку CV-квалификаторы отбрасываются во время этой проверки, что лучший способ проверить константу аргумента, который я передаю. \


    Я не смог найти способ использовать Boost для этого. Я тоже застрял, используя старую версию Boost 1.43 (проверю и уточню точное), я считаю. Если нет причин откатывать мой собственный чек, это может быть к лучшему.


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


РЕДАКТИРОВАТЬ 1


Обсудив проблему с @Nir Friedman, я понял, что нарушение правил неявного преобразования и достижение точного соответствия не то, что я хочу. Если переданный тип может быть преобразован, все должно быть хорошо. Я был бы признателен за указатель на то, как добиться этого.


ИЗМЕНИТЬ 2


Я отмечаю этот вопрос как закрытый, потому что @Sam Varshavchik ответил на точный вопрос, который я задал. Если кто-то интересуется вопросом, заданным в EDIT 1, я либо задаю его как отдельный вопрос, либо отправлю свое решение здесь для справки.

спросил(а) 2017-03-03T18:43:00+03:00 3 года, 1 месяц назад
1
Решение
62

Вы пояснили, что ваш operator() возвращает void, и вы хотите строго соответствовать сигнатуре, игнорируя преобразования типов.


Если это так, то ожидаемые результаты должны быть false true false false false false, а не true false false false false false:


 is_callable_oper<has, int&>::value

Так как has::operator() не принимает параметр int & const &, который сворачивается в int &, результат этого теста должен быть ложным.


 is_callable_oper<has, int>

Так как has действительно имеет operator(), который принимает параметр const int &, этот тест должен пройти.


Мое решение просто использует std::is_same для сравнения двух типов, а std::enable_if - с SFINAE - отказывается от кандидата разрешения шаблона.


#include <type_traits>
#include <iostream>

struct has
{
void operator()(int const&);
};

struct hasNot1
{
void operator()(int);
};

struct hasNot2
{
void operator()();
};

struct hasNot3
{
void operator()(float);
};

struct hasNot4
{
};

template<typename T, typename P, typename foo=void>
class is_callable_oper : public std::false_type {};

template<typename T, typename P>
class is_callable_oper<T, P, typename std::enable_if<
std::is_same<decltype(&T::operator()),
void (T::*)(const P &)>::value>::type>
: public std::true_type {};

int main() {
std::cout << std::boolalpha << is_callable_oper<has, int&>::value << " "
<< is_callable_oper<has, int>::value << " "
<< is_callable_oper<hasNot1, int&>::value << " "
<< is_callable_oper<hasNot2, int&>::value << " "
<< is_callable_oper<hasNot3, int&>::value << " "
<< is_callable_oper<hasNot4, int&>::value << std::endl;
return 0;
}


EDIT: используя std::void_t или разумное факсимиле, по специализации также можно сделать эту работу с перегруженными операторами:


template<typename T, typename P>
class is_callable_oper<T, P,
std::void_t<decltype( std::declval< void (T::*&)(const P &)>()=&T::operator())>>
: public std::true_type {};

ответил(а) 2017-03-03T19:45:00+03:00 3 года, 1 месяц назад
64

В более современных компиляторах типичным подходом для такого рода проблем является void_t idiom: Как работает` void_t`. Вот как все идет. Шаг 1:


template <class T>
using void_t = void; // avoid variadics for simplicity

Очевидно, что это общая и не специфическая для вашей проблемы, но она просто не входит в стандартную библиотеку до 17.


Затем мы определяем наш признак и даем определение по умолчанию как false:


template <class T, class P, class = void>
struct is_callable_oper : std::false_type {};

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

template <class T, class P>
struct is_callable_oper<T, P,
void_t<decltype(std::declval<T>()(std::declval<P>()))>>: std::true_type {};

Основная идея состоит в том, что void_t, как мы знаем, всегда будет переведен на void. Но вводимый тип не будет иметь никакого смысла, если выражение не будет действительным; в противном случае это приведет к смене замещения. Таким образом, в основном, код выше проверяет,


std::declval<T>()(declval<P>())

Является разумным для вашего типа. Это в основном обнаружение признаков, которое вы хотите. Обратите внимание, что есть некоторые тонкости, связанные с lvalues, rvalues, constness, но это было бы немного связано с тем, чтобы войти сюда.


Теперь, как отмечает Сэм в комментариях, является ли это выражение разумным или нет, подразумевается неявное преобразование. Таким образом, вывод действительно истинно true true false true false. Рабочий пример: http://coliru.stacked-crooked.com/a/81ba8b208b90a2ba.


Например, вы ожидали, что is_callable_oper<has, int>::value будет false. Однако, очевидно, можно вызвать функцию, принимающую const int& с int. Поэтому вместо этого вы становитесь правдой.

ответил(а) 2017-03-03T19:05:00+03:00 3 года, 1 месяц назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

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