Вопрос по c++11, template-meta-programming, templates, c++ – Получить тип аргумента шаблона вызываемого объекта

14

Рассмотрим следующую функцию:

template<class F>
void register_handler( F& f ) // any callable object
{
   // find out T - the argument type of f
}

Вотf это некоторый вызываемый объект, принимающий один аргумент. Это может быть указатель на функцию,std::function или результатstd::bind.

Проблема в том, как определить тип аргументаf и делать какие-то действия на основе этого типа?


Простой обходной путь - явно добавить тип в шаблон, например,

template<class T, class F> // T is the argument type of F
void register_handler( F& f )

но это кажется излишним, потому что типF уже должен содержать необходимую информацию о типеT.

Если это поможет, «потому что тип F уже должен содержать необходимую информацию о типе T». неверное предположение. R. Martinho Fernandes
Для чего вы используете информацию? Как подробно описано ниже, ваша проблема не может быть решена в целом, но проблема, которая ее мотивировала, может быть решена. Yakk - Adam Nevraumont
Хорошую справочную реализацию можно найти в RapidCheck, особенноВот Обратите внимание, что эта реализация может иметь дело только с реализованными типами аргументов (реализовано большинство примитивных типов). Unapiedra
Пожалуйста, опишите вашу проблему, а не ваше решение. "получить тип аргументаf! "- это решение какой текущей проблемы у вас есть? R. Martinho Fernandes
Вы можете специализировать шаблоны в соответствии с вашими потребностями. Я не могу вспомнить точный синтаксис и точную практику, но вы можете взглянуть на:Шаблонная специализация Надеюсь, это поможет :) Редактировать: я нашел лучшую ссылку johnkork

Ваш Ответ

5   ответов
14

Если предположить,F любой вызываемый тип, вы не можете получить его тип аргумента. Учти это:

struct callable
{
    void operator() (int);
    void operator() (float *);
    void operator() (std::string const &);
    void operator() (std::list<int> &);
};

тип аргумента здесь неоднозначен.

@DavidFeurle: чтобы выбрать перегрузку по умолчанию, я должен получить список возможных перегрузок, что невозможно. lisyarus
@lisyarus В некоторых случаях это возможно. Поэтому утверждение, что это вообще невозможно, ложно. facetus
Еще хуже в C ++ 1y с общей лямбда[](auto v) {} galop1n
@DavidFeurle: что значит сказать «заранее»? Буду рад, если вы предоставите несколько примеров кода) lisyarus
5

этоСообщение блога показывает, как реализовать некоторые черты типа функции. Они должны работать со всем вызываемым (исключение: полиморфные функторы: P). Вы можете перебирать аргументы и использовать их тип для выполнения некоторых sfinae или в качестве дополнительного аргумента шаблона.

Функциональные черты, скопированные сСообщение блога:

#include <tuple>

// as seen on http://functionalcpp.wordpress.com/2013/08/05/function-traits/
template<class F>
struct function_traits;

// function pointer
template<class R, class... Args>
struct function_traits<R(*)(Args...)> : public function_traits<R(Args...)>
{};

template<class R, class... Args>
struct function_traits<R(Args...)>
{
    using return_type = R;

    static constexpr std::size_t arity = sizeof...(Args);

    template <std::size_t N>
    struct argument
    {
        static_assert(N < arity, "error: invalid parameter index.");
        using type = typename std::tuple_element<N,std::tuple<Args...>>::type;
    };
};

// member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...)> : public function_traits<R(C&,Args...)>
{};

// const member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...) const> : public function_traits<R(C&,Args...)>
{};

// member object pointer
template<class C, class R>
struct function_traits<R(C::*)> : public function_traits<R(C&)>
{};

// functor
template<class F>
struct function_traits
{
    private:
        using call_type = function_traits<decltype(&F::operator())>;
    public:
        using return_type = typename call_type::return_type;

        static constexpr std::size_t arity = call_type::arity - 1;

        template <std::size_t N>
        struct argument
        {
            static_assert(N < arity, "error: invalid parameter index.");
            using type = typename call_type::template argument<N+1>::type;
        };
};

template<class F>
struct function_traits<F&> : public function_traits<F>
{};

template<class F>
struct function_traits<F&&> : public function_traits<F>
{};

Testcode:

#include <iostream>

class A
{
};

template <class T>
struct Functor
{
  void operator()(const T& t)
  {}
};

struct Register
{
  //int parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_same<typename function_traits<T>::template argument<0>::type, const int&>::value>::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }

  //A parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_same<typename function_traits<T>::template argument<0>::type, const A&>::value>::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }
};

void intFunc(const int&) {}
void aFunc(const A&){}

int main(int /*argc*/, char */*argv*/[])
{
  Functor<int> intFunctor;
  Functor<A> aFunctor;

  Register::RegisterFunctor(intFunctor);
  Register::RegisterFunctor(&intFunc);
  Register::RegisterFunctor(aFunctor);
  Register::RegisterFunctor(&aFunc);
  return 0;
}
Я думаю, что общая проблема в том, что мы не знаем точно, какова цель параметра типа. Если он используется для выполнения sfinae, то можно проверить, поддерживает ли функтор вызов beeing. Если это для других целей, он не может работать с полиморфными функторами. Но вариант использования может быть для поддержки только не полиморфных функторов. David Feurle
Это излишне ограничивает и не будет работать вообще; ограничения особенно проблематичны с принятием полиморфных лямбд и полиморфных вызываемых объектов в Boost или стандартной библиотеке. R. Martinho Fernandes
@ R.MartinhoFernandes это правильно. Это решение не относится к полиморфным лямбдам. В особых случаях с sfinae это можно сделать, так как вы можете проверить, поддерживает ли лямбда-вызов beeing с параметром определенного типа -> см. Мой другой ответ David Feurle
1

если F являетсяstd::functionВы должны быть в состоянии использовать егоmember type и проверьте с помощью `std :: is_same ':

template<class F>
void register_handler( F& f ) // any callable object
{
   // find out T - the argument type of f
   if(std::is_same<int, F::argument_type>::value)
   { .... }
   //etc .....

}

Готовый примерВот

но такой код может быстро стать беспорядком в обслуживании.

0

Вы можете использовать sfinae и проверить, можно ли преобразовать ваш аргумент в std :: function с необходимыми аргументами:

#include <type_traits>
#include <functional>
#include <iostream>

class A
{
};

template <class T>
struct Functor
{
  void operator()(const T& t)
  {}
};

struct Register
{
  //int parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_constructible<typename std::function<void (int)>, T>::value >::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }

  //A parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_constructible<typename std::function<void (A)>, T>::value >::type* = 0)
  {
    std::cout << "Register a func" << std::endl;
  }
};

void intFunc(int) {}
void aFunc(A){}

int main(int /*argc*/, char */*argv*/[])
{
  Functor<int> intFunctor;
  Functor<A> aFunctor;

  Register::RegisterFunctor(intFunctor);
  Register::RegisterFunctor(&intFunc);
  Register::RegisterFunctor(aFunctor);
  Register::RegisterFunctor(&aFunc);
  return 0;
}
0

В этом случае вы можете использовать очень простую библиотекуBoost.Callable Черты.

Пример использования:

#include <boost/callable_traits.hpp>
#include <iostream>
#include <tuple>

template<typename F>
void register_handler(F&)
{
    if constexpr (std::is_same_v<boost::callable_traits::function_type_t<F>, void(int&, double)>)
    {
        std::cout << "Register handler with syntax void(int&, double)" << std::endl;
    }
    else if constexpr (std::is_same_v<boost::callable_traits::function_type_t<F>, void(int)>)
    {
        std::cout << "Register handler with syntax void(int)" << std::endl;
    }
}

void func(int&, double)
{}

auto lambda = [](int) {};

int main()
{
    {
        register_handler(func);
        register_handler(lambda);
    }

    {
        using function_type = boost::callable_traits::function_type_t<decltype(func)>;
        using expected_function_type = void(int&, double);

        std::cout << std::boolalpha << std::is_same_v<expected_function_type, function_type> << std::endl;
    }
}

Чтобы получить тип функции вы можете использоватьboost::callable_traits::function_type_t<decltype(func)>.

Как вы можете видеть вmain а такжеregister_handler функции, можно сравнитьexpected_function_type тип с типом функции (boost::callable_traits::function_type_t<FUNCTION>) с помощьюstd::is_same_v "функция" ->https://en.cppreference.com/w/cpp/types/is_same

Если вы хотите запустить мой пример, скомпилируйте его с boost 1.66.0 и c ++ 17, используя, например, gcc 7.1.0.Вот Вы можете сделать это онлайн :)

Похожие вопросы