#c #templates #metaprogramming #c 11
#c #шаблоны #метапрограммирование #c 11
Вопрос:
Я пытаюсь проверить, совместим ли функтор с заданным набором типов параметров и заданным типом возвращаемого значения (то есть заданные типы параметров могут быть неявно преобразованы в фактические типы параметров и наоборот для возвращаемого типа). В настоящее время я использую для этого следующий код:
template<typename T, typename R, template<typename U, typename V> class Comparer>
struct check_type
{ enum {value = Comparer<T, R>::value}; };
template<typename T, typename Return, typename... Args>
struct is_functor_compatible
{
struct base: public T
{
using T::operator();
std::false_type operator()(...)const;
};
enum {value = check_type<decltype(std::declval<base>()(std::declval<Args>()...)), Return, std::is_convertible>::value};
};
check_type<T, V, Comparer>
Это работает довольно хорошо в большинстве случаев, однако не удается скомпилировать, когда я тестирую функторы без параметров, такие как struct foo{ int operator()() const;};
, потому что в этом случае два operator()
из base явно неоднозначны, что приводит к чему-то вроде этого:
error: call of '(is_functor_compatible<foo, void>::base) ()' is ambiguous
note: candidates are:
note: std::false_type is_functor_compatible<T, Return, Args>::base::operator()(...) const [with T = foo, Return = void, Args = {}, std::false_type = std::integral_constant<bool, false>]
note: int foo::operator()() const
Поэтому, очевидно, мне нужен другой способ проверить это для функторов без параметров. Я попытался сделать частичную специализацию is_functor_compatible
для пустого пакета параметров, где я проверяю, является ли тип amp;T::operator()
без параметров функцией-членом, которая работает более или менее. Однако этот подход, очевидно, терпит неудачу, когда проверяемый функтор имеет несколько operator()
.
Поэтому мой вопрос заключается в том, есть ли лучший способ проверить наличие оператора без параметров operator()
и как это сделать.
Ответ №1:
Когда я хочу проверить, является ли данное выражение допустимым для типа, я использую структуру, подобную этой:
template <typename T>
struct is_callable_without_parameters {
private:
template <typename T1>
static decltype(std::declval<T1>()(), void(), 0) test(int);
template <typename>
static void test(...);
public:
enum { value = !std::is_void<decltype(test<T>(0))>::value };
};
Комментарии:
1. кажется, пока работает, но, поскольку я хотел бы понять код, не могли бы вы объяснить, что именно это
decltype(std::declval<T1>()(), void(), 0)
делает?2. @Grizzly Первая часть предназначена для SFINAE в выражении, которое мы ищем.
0
В конце должен бытьint
возвращаемый тип, потому что в случаеstd::declval<T1>()()
, если часть имеетvoid
возвращаемый тип, вы не сможете различить перегрузки. Иvoid()
в середине, чтобы предотвратить неприятные вещи из-за перегруженных операторов запятой.3. это работает и для случая без параметров (используя
std::declval<T1>()(std::declval<Args>()...)
), поэтому мне даже не нужно специализироваться на конструкторах без параметров, так что большое спасибо4. @Grizzly да, это легко адаптируется к любому выражению, которое вы хотите 🙂
Ответ №2:
Вы пробовали что-то вроде:
template<size_t>
class Discrim
{
};
template<typename T>
std::true_type hasFunctionCallOper( T*, Discrim<sizeof(T()())>* );
template<typename T>
std::false_type hasFunctionCallOper( T*, ... );
После этого вы различаете возвращаемый тип
hasFunctionCallOper((T*)0, 0)
.
ОТРЕДАКТИРОВАНО (благодаря предложению Р. Мартиньо Фернандеса):
Вот код, который работает:
template<size_t n>
class CallOpDiscrim {};
template<typename T>
TrueType hasCallOp( T*, CallOpDiscrim< sizeof( (*((T const*)0))(), 1 ) > const* );
template<typename T>
FalseType hasCallOp( T* ... );
template<typename T, bool hasCallOp>
class TestImpl;
template<typename T>
class TestImpl<T, false>
{
public:
void doTellIt() { std::cout << typeid(T).name() << " does not have operator()" << std::endl; }
};
template<typename T>
class TestImpl<T, true>
{
public:
void doTellIt() { std::cout << typeid(T).name() << " has operator()" << std::endl; }
};
template<typename T>
class Test : private TestImpl<T, sizeof(hasCallOp<T>(0, 0)) == sizeof(TrueType)>
{
public:
void tellIt() { this->doTellIt(); }
};
Комментарии:
1. Похоже, это не работает, компилятору не нравится
T()()
(неудивительно, поэтому я попытался использоватьstd::declval<T>()()
вместо этого, ноhasFunctionCallOper((T*)0, 0)
, похоже, никогда не соответствует первой функции, даже если operator() существует2. @Grizzly Я признаю, что я не тестировал это; Я использовал нечто подобное для именованных функций. (Но, возможно, выражение в
sizeof
было больше похоже на(*((T*)0))()
Иначе, оно завершится неудачей, если также не будет конструктора по умолчанию.) В порядке исключения, мой ответ здесь был всего лишь предложением того, что вы могли бы попробовать; у меня не было времени экспериментировать самому, чтобы дать окончательный ответ.3. @Grizzly ОК. Я проверил это. Это работает для всех возвращаемых значений, кроме void;
sizeof
of avoid
не работает. Поэтому я отложу R. Martinho Fernandes: usingsizeof( (*((T const*)0))(), 1 )
works (или отбросьтеconst
, если вам нужен только неконстантныйoperator()
). Я отредактирую свою публикацию, чтобы добавить код, который работает (но заслуга действительно принадлежит Мартиньо за идею использования оператора запятой, за которым следует целое число).