2017-01-09 7 views
8

I以下の型特性があります。テンプレート関数に必要な引数の数はどのようにして求めることができますか?

template <class T> 
struct Arity : Arity<decltype(&T::operator())> {}; 

template <class T, class R, class... Args> 
struct Arity<R(T::*)(Args...)> { 
    static constexpr auto value = sizeof...(Args); 
}; 

template <class T, class R, class... Args> 
struct Arity<R(T::*)(Args...) const> { 
    static constexpr auto value = sizeof...(Args); 
}; 

template <class R, class... Args> 
struct Arity<R(*)(Args...)> { 
    static constexpr auto value = sizeof...(Args); 
}; 

機能は、ほとんどのユースケースのためにとる引数の数を見つけるために素晴らしい作品が、それは一つの共通のケースで失敗した:私は信じている

auto l1 = [](int, double){}; 
Arity<decltype(l1)>::value; // works, 2 

auto l2 = [](auto, auto){}; 
Arity<decltype(l2)>::value; // error: Reference to overloaded function could not be resolved; did you mean to call it? 

テンプレート型として渡される型/値に応じて、異なるオーバーロードを選択することができ、あるいは全くオーバーロードを使用できない可能性があるため、テンプレート化された関数/演算子()に対してこの作業を一般的に行うことは不可能です。また、テンプレート引数として渡す有効な型と値を知る方法もありません。しかし、それでも、私はauto引数を取るラムダの一般的なケースのためにこれを働かせたい。これをより堅牢にし、自動引数を取るラムダをカバーする方法はありますか?

+0

あなたは無資格の候補が得られにくい故障やSFINAEを参照のコンパイルに失敗しますか?関数の引数を数える方法はあると思いますが、現在のバージョンとテンプレート-λ対応のバージョンの間でうまく自動選択するかどうかはわかりません。 –

+0

@BenVoigt 'オーバーロードされた関数への参照を解決できませんでした。それを呼び出すことを意味しましたか? ' – David

+0

それでは、あなたは間違いがあります。これは、 'operator()'のテンプレート引数の有無にかかわらず動作するソリューションを作る上でもう少し苦労します。 –

答えて

4

私はここで解決策の半分を達成したと思います。固定数のパラメータまでしか動作しませんが、問題ではないほとんどのアプリケーションで使用できます。また、それはおそらく非常に単純化可能ですが、私の脳は今、トリッキーなSFINAEにはありません。

template < 
    class, std::size_t N, 
    class = std::make_index_sequence<N>, 
    class = void_t<> 
> 
struct CanCall : std::false_type { }; 

template <class F, std::size_t N, std::size_t... Idx> 
struct CanCall< 
    F, N, 
    std::index_sequence<Idx...>, 
    void_t<decltype(std::declval<F>()((Idx, std::declval<Any const&&>())...))> 
> : std::true_type { }; 

CanCall<F, N>Fが任意のタイプのNパラメータで呼び出し可能であるかどうかを返します。 Anyヘルパー・タイプにはテンプレート化された暗黙の変換演算子があり、これを使用して任意のパラメーター・タイプに変換することができます。

template <class F, std::size_t N = 0u, class = void> 
struct Arity : Arity<F, N + 1u, void> { }; 

template <class F, std::size_t N> 
struct Arity<F, N, std::enable_if_t<CanCall<F, N>::value>> 
: std::integral_constant<std::size_t, N> { }; 

template <class F> 
struct Arity<F, MAX_ARITY_PROBING, void> 
: std::integral_constant<std::size_t, ARITY_VARIADIC> { }; 

Arity<F>はちょうどFはゼロ、1、2 ...のパラメータで呼び出すことができるかどうかをチェックします。最初の肯定的なチェックが勝ちます。 MAX_ARITY_PROBINGのパラメータに達すると、Arityが取り除かれ、関数が可変であるか、まったく関数ではないと仮定します。

See it live on Coliru

+0

Oh my。素晴らしい。 '[](auto&){}'はそれを壊します。 – David

+0

@Davidはそれを修正しました! – Quentin

+0

これは '[](int &&){}'で動作しません。 – David

2

引数の型がautoのユースケースでラムダ関数を使うことはできません。このようなラムダ関数の関数は、関数テンプレートを使用して実装される可能性が最も高い。

auto l2 = [](auto, auto){}; 
Arity<decltype(l2)>::value; 

は、対象の詳細はthis answer to another SO questionを見る

したがって、decltypeを一緒に使用することができません。

+0

私はこれが答えであることをかなり確信していました。 – David

+0

これは答えよりも多くのコメントです。一つのアプローチがうまくいかないということは、実際のアプローチが存在しないことを証明することから遠い方法であることを示しています。 –

+0

また、あなたはそれを間違って分析していると思います。 'decltype(l2)'はうまくいきます。失敗した 'Arity'の中の'&T :: operator() 'です。 'l2'はテンプレートではありません(あなたのテストされていない提案が想定されているように)、むしろテンプレート' operator() 'を持つ非テンプレートクラスです。 –

関連する問題