Как определить сигнатуру функции вызываемого объекта? - PullRequest
17 голосов
/ 09 мая 2011

Есть ли в C ++ способ определить сигнатуру функции вызываемого объекта?

Рассмотрим следующее:

template< typename F >
void fun(F f)
{
    // ...
}

Предположим, что fun вызывается только с вызываемыми "вещами".

Внутри fun Я хочу знать, что является сигнатурой функции f. Это должно работать с указателями на функции, ссылками, обертками, лямбдами, привязками, объектами функций (при условии, что они имеют только один operator ()) и так далее. Я ограничен Visual Studio 2010 SP 1, но меня интересуют стандартные решения, даже если я не работаю с этим компилятором.

(сигнатура функции Return_Type ([Arg1_Type [, Arg2_Type [, ... ] ] ]); такая же, как для std::function / boost::function.)

Частичное решение, заключающееся в знании по крайней мере возвращаемого значения f, имеет некоторое значение для. (Я пробовал std::result_of, но не смог заставить его работать ни в коем случае.)

Ответы [ 4 ]

3 голосов
/ 10 мая 2011

Пытаясь решить эту проблему, я нашел следующее частичное решение:

#include <cstdlib>
#include <functional>
#include <iostream>
#include <typeinfo>
#include <boost/bind.hpp>
#include <boost/function.hpp>

template< typename T >
struct identity
{
    typedef T type;
};

// ----------
// Function signature metafunction implementation
// Also handler for function object case
// ----------

template< typename T >
struct function_signature_impl
    : function_signature_impl< decltype( &T::operator() ) >
{
};

// ----------
// Function signature specializations
// ----------

template< typename R >
struct function_signature_impl< R () >
    : identity< R () >
{
};

template< typename R, typename A1 >
struct function_signature_impl< R ( A1 ) >
    : identity< R ( A1 ) >
{
};

template< typename R, typename A1, typename A2 >
struct function_signature_impl< R ( A1, A2 ) >
    : identity< R ( A1, A2 ) >
{
};

// ----------
// Function pointer specializations
// ----------

template< typename R >
struct function_signature_impl< R ( * )() >
    : function_signature_impl< R () >
{
};

template< typename R, typename A1 >
struct function_signature_impl< R ( * )( A1 ) >
    : function_signature_impl< R ( A1 ) >
{
};

// ----------
// Member function pointer specializations
// ----------

template< typename C, typename R >
struct function_signature_impl< R ( C::* )() >
    : function_signature_impl< R () >
{
};

template< typename C, typename R, typename A1 >
struct function_signature_impl< R ( C::* )( A1 ) >
    : function_signature_impl< R ( A1 ) >
{
};

template< typename C, typename R >
struct function_signature_impl< R ( C::* )() const >
    : function_signature_impl< R () >
{
};

template< typename C, typename R, typename A1 >
struct function_signature_impl< R ( C::* )( A1 ) const >
    : function_signature_impl< R ( A1 ) >
{
};


// ----------
// Function signature metafunction
// ----------

template< typename T >
struct function_signature
    : function_signature_impl< T >
{
};


// ----------
// Tests
// ----------

template< typename F >
void test( F f )
{
    typedef function_signature< F >::type signature_type;

    std::cout << typeid( F ).name() << std::endl;
    std::cout << '\t' << typeid( signature_type ).name() << std::endl;
    std::cout << std::endl;
}


int foo( int )
{
    return 0;
}

struct bar
{
    int operator ()( int )
    {
        return 0;
    }

};

struct cbar
{
    int operator ()( int ) const
    {
        return 0;
    }

};

struct abar1
{
    int operator ()( int ) const
    {
        return 0;
    }

    int operator ()( int )
    {
        return 0;
    }

};

struct abar2
{
    int operator ()( int )
    {
        return 0;
    }

    int operator ()( double )
    {
        return 0;
    }

};

struct mem
{
    int f( int ) const
    {
        return 0;
    }
};


int main()
{
    test(
        []( int ) -> int { return 0; }
    );

    test(
        foo
    );

    test(
        &foo
    );

    test(
        bar()
    );

    test(
        cbar()
    );

    test(
        std::function< int ( int ) >( &foo )
    );

    test(
        boost::function< void ( int ) >( &foo )
    );

    /*
    test(
        std::bind( &mem::f, mem(), std::placeholders::_1 )
    );
    */

    /*
    test(
        boost::bind( &mem::f, mem(), _1 )
    );
    */

    /*
    test(
        abar1()
    );
    */

    /*
    test(
        abar2()
    );
    */

    return EXIT_SUCCESS;
}

(код для проверки аргументов inproper не добавлен.)

Идея состоит в том, что function_signature< decltype( f ) >::typeдолжна быть подпись вызова f( ... ), где это "..." является подписью.В частности, это означает, что указатель на функцию-член здесь является недопустимым аргументом (хотя код не проверяет это), поскольку такой указатель не может быть «вызван» напрямую.

В конце тесты, которые не пройдены (в VS2010).Все из-за перегрузки operator ().И это делает этот код в основном бесполезным, так как он не будет работать с результатом bind.Но, возможно, это может быть доработано.


Ответ на вопрос Андре Бергнера:

function_signature_impl никогда не происходит от самого себя.Это шаблон типа, который означает только слабосвязанное семейство реальных типов.Но фактические типы (даже если они принадлежат к одному и тому же семейству) являются разными типами.

&T::operator() - это указатель на оператор вызова (operator()) типа T - очевидно.По сути, просто указатель на функцию-член (где функция-член оказывается оператором вызова).В то время как decltype это тип этого указателя.Это может показаться незначительным (особенно если type_info::name обоих показывает одно и то же), но для шаблонов это имеет значение, поскольку один является указателем, а другой - типом (очевидно).

Этот «случай» необходим дляобложка для функторов (типы объектов, которые можно вызвать).Обратите внимание, что этот неспециализированный function_signature_impl используется только в том случае, если аргумент шаблона T не соответствует ничему другому среди перечисленных «случаев».

Надеюсь, я понял это сразу после этого долгого времени.Хотя я не уверен, что когда-либо действительно и полностью понял это.Код был немного результатом экспериментов.

3 голосов
/ 09 мая 2011

В компиляторах, совместимых с C ++ 0x, вы можете по крайней мере получить тип результата f(), используя decltype(f()). Visual C ++ 2010 должен поддерживать decltype, хотя я сам еще не проверял его. Что касается получения типов аргументов, я не уверен, есть ли способ, который работал бы с указателями на функции.

Редактировать

Boost.Function, кажется, выяснил это, по крайней мере на некоторых компиляторах (например, он не работает на старых версиях VC ++ или Borland C ++). Он может переносить указатели на функции и извлекать для них аргументы. Однако решение кажется довольно сложным и включает определение нескольких шаблонов с помощью Boost.PP. Если вам хочется заново реализовать все, вы, конечно, можете попробовать это, но я думаю, что вы также можете просто использовать фиктивную оболочку Boost.Function, чтобы упростить задачу, например, boost::function<decltype(f)>::second_argument_type чтобы получить второй тип аргумента.

2 голосов
/ 09 мая 2011

Вы можете посмотреть на типы функций повышения:

http://www.boost.org/doc/libs/1_46_1/libs/function_types/doc/html/boost_functiontypes/introduction.html

1 голос
/ 19 февраля 2018

Этот ответ только что дал мне SlashLife на freenode ## c ++:

template <typename T, typename Signature>
struct signature_impl;

template <typename T, typename ReturnType, typename... Args>
struct signature_impl<T, ReturnType(T::*)(Args...)>
{
    using type = ReturnType(Args...);
};

template <typename T>
using signature_t = signature_impl<T, decltype(&T::operator())>;

Предостережение заключается в том, что он работает только при наличии уникального operator() и не работает для лямбд.

...