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

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

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

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, но не смог заставить его работать в любом случае, я попробовал.)

Ответ 1

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

Изменить

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

Ответ 3

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

#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( ... ), где это "..." является сигнатурой. Это означает, в частности, что указатель на функцию-член здесь является недопустимым аргументом (хотя код не проверяет на это), поскольку этот указатель не может быть непосредственно вызван.

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


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

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

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

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

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