altebute.hatenablog.com

犬も歩けば規格にあたる

高階関数内で、引数の引数の型を取得する

いなむ先生とバンビちゃん先生にご助力頂いてやっと書けたコードです。多謝!

2016.01.03 追記 VC++では関数ポインタの型推論について、Variadic Templateのマッチングの優先順位がおかしいため正常に動作しません。

まず以下のコードについて考える。

template< typename Arg, typename Func >
void f( const Func& func )
{
    Arg arg;
    func( arg );
}

arg が未初期化になる場合についてはとりあえず考えない。 上記のコードでは、 Arg の型を明示的に指定しなくてはならない。 なんとかして、 Func から Arg の型を導出したい。

そこで、 std::function を引数とする関数に書き換える。

#include <functional>

template< typename R, typename A >
void f( const std::function< R(A) >& )
{
    A arg;
}

int main()
{
    std::function< void(int) > a;
    f( a );
}

上記のコードは通る。確かに通る。 が、lambdaや、ユーザ定義の関数オブジェクトを投げると、 std::function の型を明示的にしてやらないと推論に失敗して死ぬ。 型の指定を省略したいのに、これでは本末転倒である。

struct s
{
    void operator()( int ){}
};

int main()
{
    auto l = [](int){};
    auto o = s{};
    f< void, int >( l ); // ok
    f< void, int >( o ); // ok
    f( l ); // ng
    f( o ); // ng
}

しかも、 std::function は小さくないオーバーヘッドが生じる。 C++であるからには、可能な限りゼロオーバーヘッドの原則を貫きたい。


まず、関数ポインタについて。 引数と戻り値の型を取得する。

#include <functional>
#include <type_traits>
#include <iostream>
#include <boost/type_index.hpp>

namespace impl
{
    template< typename R, typename A > auto result_type_helper(   R( * )( A ) ) -> R;
    template< typename R, typename A > auto argument_type_helper( R( * )( A ) ) -> A;
}

template< typename F > using result_type   = decltype( impl::result_type_helper(   std::declval< F >() ) );
template< typename F > using argument_type = decltype( impl::argument_type_helper( std::declval< F >() ) );

void f( int ){}

int main()
{
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    using ft = decltype( f );
    cout << type_id_with_cvr< result_type<   ft > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< ft > >().pretty_name() << endl;
}

同様に関数オブジェクト。

...

namespace impl
{
    template< typename R, typename F, typename A > auto result_type_helper(   R( F::* )( A ) ) -> R;
    template< typename R, typename F, typename A > auto argument_type_helper( R( F::* )( A ) ) -> A;
    template< typename R, typename F, typename A > auto result_type_helper(   R( F::* )( A )const ) -> R;
    template< typename R, typename F, typename A > auto argument_type_helper( R( F::* )( A )const ) -> A;
}

template< typename F > using result_type   = decltype( impl::result_type_helper(   &F::operator() ) );
template< typename F > using argument_type = decltype( impl::argument_type_helper( &F::operator() ) );

struct s
{
    void operator()(char){}
};

int main()
{
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    auto l = [](double){};
    using lt = decltype( l );
    cout << type_id_with_cvr< result_type<   lt > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< lt > >().pretty_name() << endl;
    cout << type_id_with_cvr< result_type<   s  > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< s  > >().pretty_name() << endl;
}

メンバ関数ポインタを扱うため、人によっては見慣れない演算子 ::* が使われている。

ただし、関数オブジェクト内で関数がオーバーロードされている場合はうまく動かない。 そもそも、オーバーロードされている場合は明示的に指定しないと型が取得出来ないのは当然なので、 以下のように明示的に取得して使用する。

...

namespace impl
{
    template< typename R, typename F, typename A > auto result_type_helper(   R( F::* )( A ) ) -> R;
    template< typename R, typename F, typename A > auto argument_type_helper( R( F::* )( A ) ) -> A;
}

template< typename F > using result_type   = decltype( impl::result_type_helper(   std::declval< F >() ) );
template< typename F > using argument_type = decltype( impl::argument_type_helper( std::declval< F >() ) );

struct s
{
    void operator()(char){}
    void operator()(float){}
};

int main()
{
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    void( s::* c )( char  ) = &s::operator();
    void( s::* f )( float ) = &s::operator();
    using ct = decltype( c );
    using ft = decltype( f );
    cout << type_id_with_cvr< result_type<   ct > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< ct > >().pretty_name() << endl;
    cout << type_id_with_cvr< result_type<   ft > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< ft > >().pretty_name() << endl;
}

最後に、これらについて type_traits を用いて分岐を行う。 さらに、引数の型が void の場合と、2つ以上の引数に対応する。

#include <functional>
#include <type_traits>
#include <iostream>
#include <tuple>
#include <boost/type_index.hpp>

namespace impl
{
    template< typename P > struct function_pointer
    {
        template< typename R                               > static auto argument( R( * )( void       ) ) -> void;
        template< typename R                               > static auto result(   R( * )( void       ) ) -> void;
        template< typename R, typename A                   > static auto argument( R( * )( A          ) ) -> A;
        template< typename R, typename A                   > static auto result(   R( * )( A          ) ) -> R;
        template< typename R, typename A, typename... Args > static auto argument( R( * )( A, Args... ) ) -> std::tuple< A, Args... >;
        template< typename R, typename A, typename... Args > static auto result(   R( * )( A, Args... ) ) -> R;
        using argument_type = decltype( argument( std::declval< P >() ) );
        using result_type   = decltype( result(   std::declval< P >() ) );
    };
    
    template< typename P > struct member_function_pointer
    {
        template< typename R, typename C                               > static auto argument( R( C::* )( void       )      ) -> void;
        template< typename R, typename C                               > static auto result(   R( C::* )( void       )      ) -> R;
        template< typename R, typename C                               > static auto argument( R( C::* )( void       )const ) -> void;
        template< typename R, typename C                               > static auto result(   R( C::* )( void       )const ) -> R;
        template< typename R, typename C, typename A                   > static auto argument( R( C::* )( A          )      ) -> A;
        template< typename R, typename C, typename A                   > static auto result(   R( C::* )( A          )      ) -> R;
        template< typename R, typename C, typename A                   > static auto argument( R( C::* )( A          )const ) -> A;
        template< typename R, typename C, typename A                   > static auto result(   R( C::* )( A          )const ) -> R;
        template< typename R, typename C, typename A, typename... Args > static auto argument( R( C::* )( A, Args... )      ) -> std::tuple< A, Args... >;
        template< typename R, typename C, typename A, typename... Args > static auto result(   R( C::* )( A, Args... )      ) -> R;
        template< typename R, typename C, typename A, typename... Args > static auto argument( R( C::* )( A, Args... )const ) -> std::tuple< A, Args... >;
        template< typename R, typename C, typename A, typename... Args > static auto result(   R( C::* )( A, Args... )const ) -> R;
        using argument_type = decltype( argument( std::declval< P >() ) );
        using result_type   = decltype( result(   std::declval< P >() ) );
    };
    
    template< typename C > struct function_object
    {
        using argument_type = typename member_function_pointer< decltype( &C::operator() ) >::argument_type;
        using result_type   = typename member_function_pointer< decltype( &C::operator() ) >::result_type;
    };
    
    template< typename F > struct function_traits
    : public std::conditional_t
    <
        std::is_member_function_pointer< F >::value,
        member_function_pointer< F >,
        std::conditional_t
        <
            std::is_pointer< F >::value,
            function_pointer< F >,
            function_object< F >
        >
    >
    {};
}

template< typename F > using argument_type = typename impl::function_traits< std::decay_t< F > >::argument_type;
template< typename F > using result_type   = typename impl::function_traits< std::decay_t< F > >::result_type;

struct s
{
    void operator()(int){}
};

struct o
{
    void operator()(char){}
    void operator()(void){}
};

void f( float, double ){}

int main()
{
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    
    auto l = [](long){};
    void( o::* c )( char ) = &o::operator();
    void( o::* v )( void ) = &o::operator();
    
    using ft = decltype( f );
    using lt = decltype( l );
    using ct = decltype( c );
    using vt = decltype( v );
    
    cout << type_id_with_cvr< result_type<   s  > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< s  > >().pretty_name() << endl;
    cout << type_id_with_cvr< result_type<   lt > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< lt > >().pretty_name() << endl;
    cout << type_id_with_cvr< result_type<   ct > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< ct > >().pretty_name() << endl;
    cout << type_id_with_cvr< result_type<   vt > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< vt > >().pretty_name() << endl;
    cout << type_id_with_cvr< result_type<   ft > >().pretty_name() << endl;
    cout << type_id_with_cvr< argument_type< ft > >().pretty_name() << endl;
}

上記の関数は、引数が2つ以上の場合は std::tuple を戻す。 gcc 5.2.0 では完動するが、clang 3.8.0では std::tuple の型名を取得すると何故か以下の実行時エラーで例外が送出される。

terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<std::runtime_error> >'
  what():  Type name demangling failed