2017-04-03 13 views
4

クラス用の単純なテキストフォーマッタを実装しています。その中のmain関数は、連結される値のリストを受け取ることができます。または、オプションで、パラメータがostreamのフレンズでない場合は、他のすべてのパラメータをstd :: stringに変換する最初のパラメータとして変換関数を受け入れます。C++のVariadicテンプレートで、呼び出し可能な型と呼び出し不可能な型を選択するための洗練されたソリューションがありますか?

次のコードはアイデアを示していますが、コンパイルされません。わかりやすくするために、この例ではcoutに出力します。

struct formater{ 
    template<typename P, typename... PS> 
    void format(const P& p, const PS&... ps){ 
     if (std::is_convertible<P, std::function<void()>>::value){ 
      cout << p(ps...) << endl; 
     } else { 
      cout << p; 
      log(ps...); 
     } 
    } 
}; 

コードがコンパイルされない理由は、Pが呼び出し可能であるならば、それは「他」ブランチでcoutに出力することができません、あり、そしてPは呼び出し可能でない場合、それはPを教えてくれますです呼び出し可能でなく、 "then"ブランチのps ...パラメータを受け取ることができません。

私はenable_ifを使用すると考えましたが、条件の両方のケース(TとF)を定義しているので、同じ関数の再定義を得てコンパイルも失敗します。

私はstatic_ifを模倣しようとする可能性がありますが、それは全くエレガントに見えません。

Pが呼び出し可能でSFINAEであることを確認するうまい方法があるのだろうかと思います。たぶん、P、(PS ...) - > std :: stringのパラメータ型を知っていることを利用しているかもしれません。

+1

は' P'ことになっていますか? – cdhowie

+0

'PARAM'が' const PS&... 'で呼び出し可能かどうか、あるいは 'PARAM'がまったく呼び出せるかどうか(つまり関数ポインタか関数オブジェクトか)を知りたいですか? –

+0

この特定のシナリオでは、最初のオプションが好きです。とにかく、私は確かに第二のものについて不思議です。 – dvicino

答えて

4

二つの問題があります。まず、あなたが言ったのと同じように、ifはランタイムブランチだけです。次に、呼び出し可能なオブジェクトをチェックしていませんが、オブジェクトが引数なしで呼び出し可能かどうかを確認しています。引数を取らなければならない呼び出し可能オブジェクトは、テストに合格しません。

私はまずこの特性を実装します。これはC++ 17のために必要ではないことに注意してください:

template<typename, typename = void> 
struct is_callable : std::false_type {}; 

template<typename F, typename... Args> 
struct is_callable<F(Args...), void_t<decltype(std::declval<F>()(std::declval<Args>()...))>> : std::true_type {}; 

その後、あなたはstd::enable_ifを使用することができます。

struct Formatter { 
    template<typename F, typename... Args> 
    auto format(F func, Args&&... args) -> std::enable_if_t<is_callable_v<F(Args...)>> { 
     std::cout << func(std::forward<Args>(args)...); 
     std::cout << std::endl; 
    } 

    template<typename T> 
    auto format(T&& value) -> std::enable_if_t<!is_callable_v<T()>> { 
     std::cout << std::forward<T>(value); 
     std::cout << std::endl; 
    } 
}; 

あなたはこのようなvoid_tを実装することができます:あなたが行くことができます

template<typename...> 
using void_t = void; 

をこのライブの例を確認してください:Live at coliru

0場合と std::is_invocable constexprの

C++ 17のことをご了承ください、あなたがしている:私は `PARAM`を想定

struct Formatter { 
    template<typename F, typename... Args 
    void format(F&& param, Args&&... args) { 
     if constexpr (std::is_invocable_v<F, Args...>) { 
      std::cout << std::invoke(std::forward<F>(param), std::forward<Args>(args)...); 
     } else { 
      std::cout << std::forward<F>(param); 
      log(std::forward<Args>(args)...); 
     } 
    } 
}; 
+0

最初の解決策は、100%C++ 14ではありません(void_tはC++ 17です)。しかし、私は、C++ 1zのサポートを使用してそれを試してコンパイルするために小さな変更を加えなければなりませんでした。 ここに私は変更の例を貼り付けました: 例はコンパイルされますが、結果は期待されたものではありません: 私は1を取得したい、テストとHeyを渡します。そして私は、if-constexprのテストについて、1 を渡すと、1を取得します。 エラー:関数パラメータパックの 'PS'に展開されていないパラメータパックが含まれていません – dvicino

+1

void_tの実装は、コンパイルのトリックを行いますしかし、C++ 14では、この例では間違った結果が得られています。 場合 "自動fs_no_param = []() - >のstd ::文字列{リターン "HEY";};"値としてキャストされ、 "HEY"の代わりに "1"が出力されます。なしのparamsの機能との競合がある理由:( – dvicino

+0

は、私はちょうど2番目のオーバーロードは、単一のTを取って気づいた、thatsの。SFINAEだけのparamsの量に解決されています。私はTに拡張する場合...そこが曖昧になります。 – dvicino

1

あなたはstd::enable_ifを使用することができます。

struct formater{ 
    template<typename P, typename... PS> 
    std::enable_if<std::is_convertible<PARAM, std::function<void(PS...)>>::value, void>::type 
    format(const PARAM& p, const PS&... ps){ 
     cout << p(ps...) << endl; 

    } 

    template<typename P, typename... PS> 
    std::enable_if<!std::is_convertible<PARAM, std::function<void(PS...)>>::value, void>::type 
    format(const PARAM& p, const PS&... ps){ 
     cout << p; 
     log(ps...); 

    } 
}; 
+0

'std :: function'を使ったテストは、パラメータを取るべきであるので正しくありません。それを 'std :: function ' –

+0

に変更してください。私は以下の完全版をテストのために使用します。 https:// pastebin。COM/fJp3LWx1 それはコンパイルされません、2番目の呼び出しは、まだ(打ち鳴らすを使用して)連結に行くことにしよう。 – dvicino

+0

しかし...それは完璧に動作するようだ。ラムダの誤植... [this](https://wandbox.org/permlink/3kkLegDWEDpQNWZC)をチェックしてください。 – dodomorandi

関連する問題