Как определить тип возвращаемого значения функтора, который принимает этот тип для параметра? - PullRequest
1 голос
/ 17 марта 2020

Предположим, мне нужна следующая функция:

template <typename Op, typename T> T foo(Op op) { }

Предполагается, что Op имеет следующий метод:

T Op::operator()(T&);

(это простой случай; на самом деле он может иметь больше параметров но их типы мне известны).

Теперь я хочу установить значение по умолчанию для параметра T. Проблема в том, что для его искусственного вызова (например, как в std::result_of<Op::operator()(int&)> мне нужно иметь тип параметра - это именно тот тип, который мне не хватает.

Можно ли определить T из Op? Так что я могу, например, позвонить:

foo( [](int& x){ return x++; } );

Я заинтересован в решении C ++ 11, если вам нужна возможность более поздней стандартной версии, это также интересно (особенно объяснение, почему это так).

Примечание: если Op имеет несколько совместимых операторов () 's taking references to different types, I'm ok with having to specify T`, конечно, - но компиляция должна пройти, когда я это сделаю.

Ответы [ 3 ]

4 голосов
/ 17 марта 2020

Если объект функции имеет ровно одну подпись, вы можете обнаружить эту подпись по decltype(&T::operator()) и сделать из этого вывод:

template<class T> struct X : X<decltype(&T::operator())> {}; // #1
template<class T> struct X<T(T&) const> { using type = T; }; // #2
template<class C, class M> struct X<M (C::*)> : X<M> {}; // #3
// add more specializations for mutable lambdas etc. - see example

template <typename Op, typename T = typename X<Op>::type>
T foo(Op op) { /* ... */ }

Пример (C ++ 11).


Это серия преобразований типов, выраженная как частичные шаблонные специализации. Из лямбды с анонимным типом <lambda> мы извлекаем ее тип оператора вызова функции в #1, давая тип указателя на функцию-член, такой как int (<lambda>::*)(int&) const, что соответствует #3, что позволяет нам отбрасывать тип класса, давая отвратительная тип функции int(int&) const, которая соответствует #2, что позволяет нам извлечь аргумент и тип возвращаемого значения, представленные как X::type, которые видимы для исходного экземпляра X через наследование. Использование частичных специализаций одного и того же шаблона для вычислений разных типов - это хитрость code-golf , и вы можете избежать этого (и использовать более выразительные имена) в производственном коде.


Если, с другой стороны, объект функции имеет более одной подписи (параметры шаблона, параметры по умолчанию, overloaded, объекты функций, созданные вручную, и т. Д. c.), Это не будет работать; вам нужно будет подождать, пока более сильные формы рефлексии войдут в язык.

1 голос
/ 17 марта 2020

Существование шаблонных функций показывает, что на вопрос вообще невозможно ответить:

struct post_increment
{
    template<typename T>
    T operator()(T& t) const
    {
        return t++;
    }
};

foo(post_increment{});
// or in C++14: foo([](auto& t) { return t++; });

Результат функции foo, переводящей отображение T& в T для некоторого набора типов T сам по себе полиморф c. В некоторых случаях это выражается даже в C ++ 11:

template<typename Op>
struct foo_result_t
{
    template<typename T>
    operator T() const
    {
        T t {};
        op(t);
        return t;
    }

    Op op;
};

template <typename Op>
foo_result_t<Op> foo(Op op)
{
     return {op};
}

int i = foo(post_increment{}); // use it with an int
0 голосов
/ 17 марта 2020

Другое решение в случае ровно одной подписи.

Другое ... ну ... почти то же самое решение ecatmur, но основанное на функции шаблона, для вывода типов вместо класса шаблона.

Все, что вам нужно, это объявленная функция две объявленные функции, которые определяют типы (и возвращают тот же тип возврата, что и operator(); вы можете также разработать другую функцию для возврата, например, std::tuple<Args...>, если вам нужно извлечь Args... типов)

template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...) const);

template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...));

и using (небольшой decltype() бред) для извлечения R типа

template <typename T>
using RetType = decltype(deducer(std::declval<decltype(&T::operator())>()));

Ниже приведен полный пример C ++ 11

#include <utility>

struct A
 {
   long operator() (int, char, short)
    { return 0l; }
 };

template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...) const);

template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...));

template <typename T>
using RetType = decltype(deducer(std::declval<decltype(&T::operator())>()));

int main ()
 {
   auto f = [](int& x) { return x++; };

   static_assert( std::is_same<RetType<A>, long>::value, "!" );
   static_assert( std::is_same<RetType<decltype(f)>, int>::value, "!" );
 }

Если вы также хотите управлять volatile операторами, вы должны добавить два других deducer()

template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...) volatile const);

template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...) volatile);
...