質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

メタプログラミング

メタプログラミングとは、プログラミング技法の一つ。プログラムをプログラミングすることを指します。他のプログラムや、そのプログラム自体を操作・出力するメタプログラムを記述する作業をメタプログラミングと呼びます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

4回答

1092閲覧

可変長テンプレートでメタ関数を使いたい

s8079

総合スコア36

C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

メタプログラミング

メタプログラミングとは、プログラミング技法の一つ。プログラムをプログラミングすることを指します。他のプログラムや、そのプログラム自体を操作・出力するメタプログラムを記述する作業をメタプログラミングと呼びます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2020/02/01 05:17

編集2020/02/01 08:19

前提・実現したいこと

可変長テンプレート引数の型がすべて整数型の場合に処理を分けたいです.
引数が1つのときは問題ありませんが,引数が2つ以上になると思い通りの動作になりません.
下記のソースコードではすべて「整数じゃないよ」を出力してしまいます.
今回はパラメータパックをまとめて使いたいので,パラメータを分解せずに済む方法を探しています.
よろしくお願いします.

該当のソースコード

C++

1#include <iostream> 2#include <utility> 3 4template<typename... T, typename std::enable_if<std::is_integral<T...>::value, std::nullptr_t>::type = nullptr> 5void func(T... t) { 6 std::cout << "整数だよ" << std::endl; 7} 8 9template<typename... T> 10void func(T... t) { 11 std::cout << "整数じゃないよ" << std::endl; 12} 13 14int main() { 15 func(10, 10); // 「整数だよ」を出力できるようにしたい. 16 func(10, 3.14); // 未定義動作.できればエラーにしたい. 17 func(3.14, 3.14); // 「整数じゃないよ」を出力. 18 return 0; 19}

補足情報(FW/ツールのバージョンなど)

C++11

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

s8079

2020/02/01 07:11

func(10, 10)(可変長引数がすべて整数)のときに「整数だよ」と出力するにはどうすれば良いかという質問です.
guest

回答4

0

ベストアンサー

質問文中のコードは std::is_integral に複数の型引数を渡そうとしていますが、ひとつの型しか受け取れないからです。 なのでその候補は常に失敗し、もう一方の候補が常に選ばれるという動作になります。 (常に展開に失敗するテンプレートであって、テンプレート自体はエラーではありません。 これは展開フェイズに関するややこしいルールが関係するのでここでは説明しません。)

C++17 からは fold expression が使えるのでもう少し簡単なのですが、 C++11 の範囲内でやるとなると再帰的なテンプレートの展開は必要です。 分解せずにやる方法はありません。

分解して判定する処理をする処理をトレイツにまとめることはできます。

#include <iostream> #include <type_traits> template<class ...T> class is_integral_all { private: template<class U, class... W> struct helper { static constexpr std::size_t value = std::is_integral<U>::value + helper<W...>::value; }; template<class U> struct helper<U> { static constexpr std::size_t value = std::is_integral<U>::value; }; public: static constexpr bool value = helper<T...>::value == sizeof...(T); }; template<typename... T, typename std::enable_if<is_integral_all<T...>::value, std::nullptr_t>::type = nullptr> void func(T...) { std::cout << "整数だよ" << std::endl; } template<typename... T, typename std::enable_if<not is_integral_all<T...>::value, std::nullptr_t>::type = nullptr> void func(T...) { std::cout << "整数じゃないよ" << std::endl; } int main() { func(10, 10); // 「整数だよ」を出力できるようにしたい. func(10, 3.14); // 未定義動作.できればエラーにしたい. func(3.14, 3.14); // 「整数じゃないよ」を出力. return 0; }

ここではコードを簡潔に理解しやすくするため、型が違うものが混ざっている場合をエラーとして弾いていませんが、仕組みを理解すれば簡単だと思うので演習の題材にでもしてください。

投稿2020/02/01 08:27

SaitoAtsushi

総合スコア5437

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

s8079

2020/02/01 11:06

再帰でboolを足し合わせてパラメータパックの要素数と比較するというのは非常にスマートな書き方に見えます. エラー処理するために2ヶ所書き直してみたのですが,テンプレート引数の数が少なすぎてオーバーロードできないという内容のエラーが発生します. 再帰の書き方が間違っているのでしょうか. template<class U, class... W> →template<class U, class V, class... W> std::is_integral<U>::value + helper<W...>::value; →std::is_integral<U>::value + helper<V, W...>::value;
SaitoAtsushi

2020/02/01 12:08

オーバーロードではありません。 特殊化 (この場合は部分特殊化) です。 下の helper は上の helper の特別な場合として定義しているので、上の helper を修正したらそれに合致するように下の helper も修正する必要があります。
s8079

2020/02/01 13:03

エラーに「オーバーロード」と出てきたのでそのまま書いてしまいましたが,確かに部分特殊化ですね. 下のhelperもテンプレート引数をUとVにすれば確かに動作しました. ただ,これではテンプレート引数の最小個数が2個になってしまい,テンプレート引数が1個のときにコンパイルすることができません.
SaitoAtsushi

2020/02/01 13:49 編集

型を比較するためには比較すべきふたつを用意しないといけないのでまずは型引数を増やしてみたということですね? そういうやり方でもやれるんですが、考慮すべきことが多いと慣れない内は混乱すると思うので、まずは「すべての型が同じかどうか判定するトレイト」を別に作ってみるとよいと思います。 そして template<class...W> のパターンを軸にしてそれを特殊化していけばよいです。 より一貫性を言うなら型引数がゼロ個のときも「型が全て同じ」ということになりますね。
s8079

2020/02/02 05:12 編集

テンプレート引数が2個以上の場合のみ正常に動作するis_same_allは作ることができました. ただ,これもテンプレート引数が2個未満のときはコンパイルできません. 試行錯誤でよく解らなくなってきたので解らないことを解らないなりに列挙してみました. よろしくお願いします. ・エラーを発生させるために使うのはstatic_assertなのかstd::enable_ifなのか ・上記のエラー処理はどこに書くのか ・要素数で場合分けするには三項演算子を使うのか関数を定義するのか (C++17ではないのでラムダ式は使えない.) →static constexpr std::size_t value = sizeof...(T) < 2 ? /*処理*/ : /*処理*/; ・そもそも要素数で場合分けは必要なのか
SaitoAtsushi

2020/02/02 08:26

どのような仕様にするかによるのですが、ここでは標準ライブラリのトレイトの習慣に合わせるという前提をおきます。 あえて習慣に反することをしたいという場合には必ずしもあてはまらないことはあります。 static_assert はそれの第一引数が偽となるように展開されたらその時点で問答無用でコンパイル全体が失敗です。 std::enable_if は実引数をテンプレートにマッチするかどうかを制御する仕組みであり、そのテンプレートで失敗したら次の候補の展開に移行 (これがいわゆる SFINAE) します。 どちらもエラーを発生させるという意味では同じですが、異なる性質を持つので使い分けが必要です。 真偽のどちらかがエラーであるのならそれを判断するのはトレイトを使う側であるべきです。 プログラムの部品はなるべく汎用的であるのが好ましく、 is_same_all を使うときに「型が揃ったときをエラーにしたい」ということもありえることです。 部品の側で勝手にエラーかどうかを決めるべきではないので、トレイトは SFINAE のために std::enable_if を使うことはあってもいきなり static_assert でエラーにするべきではありません。 ただ、判定結果どうこう以前にトレイトの使い方が間違っている場合 (引数の個数が想定と違うなど) はほっといてもエラーにはなりますが、 static_assert でより親切なエラーメッセージを出すのは良い習慣だと思います。 (が、練習では後回しにしてもよいことかもしれません。) 要素数で分けるのは特殊化の引数の個数の違いでやるのが簡単です。 上記で template<class...W> のパターンを軸にすればよいと述べたのは、引数をいくらでも受け付けるパターンを基本形にすれば引数の数が違うパターンはどれもその特殊化として定義可能だからです。 私が回答中で提示した is_integral_all の場合は引数が多数ある場合を繰り返しながら、残ったのが最後のひとつのときを終了条件としているので基本形とその特殊化というふたつのパターンしか有りませんが、特殊化は (重複しなければ) いくつあってもよいのです。 要素数での場合分けは、これから s8079 さんが作ろうとしている is_same_all を型引数ひとつ以下の場合にも対応させようとするならば必要だと思います。 「比較」をするトレイトですから、型引数ひとつの場合にどのような意味付けをするかということになるわけで「比較対象がないけど型はそろっているとみなす」という特別な場合だと考えることが出来ます。 特別な場合なので特別な定義が必要です。
SaitoAtsushi

2020/02/03 00:32

場合分けを増やさないで対処する方法を思いつきました。 最初の型引数と同じものをパラメータパックの先頭にもうひとつ補ってから比較処理の中に入れれば型引数がひとつのときのも対処できますね。
s8079

2020/02/07 12:49 編集

おかげさまで一応プログラムを完成させることができました. ありがとうございました. 結局,当初考えていた三項演算子等での要素数の場合分けの方法は分かりませんでした. 実装できた方法を簡単に記載しておきますので,改良の余地等がありましたらコメントをお願いします. 引数が1個の場合(引数が0個の場合はエラー)のis_same_allを実装しました. ・template<class ...T> class is_same_all ・template<class T> class is_same_all<T> // 常にtrue 目的の関数でis_same_allとis_integral_allを使用しました. ・template<typename... T, typename std::enable_if<is_same_all<T...>::value && is_integral_all<T...>::value, std::nullptr_t>::type = nullptr> void func(T...) ・template<typename... T, typename std::enable_if<is_same_all<T...>::value && !is_integral_all<T...>::value, std::nullptr_t>::type = nullptr> void func(T...)
SaitoAtsushi

2020/02/08 05:32

条件演算子を使った方法というのは、たとえば static constexpr bool value = sizeof...(T) < 2 ? 1 : helper<T...>(); みたいな方法ということですね? このとき、式を評価するためにはテンプレートが展開されていなければなりません。 テンプレートが展開できなければエラーです。 展開した結果を評価した値を使うか使わないかということは条件演算子で制御できますが、テンプレートを展開するかどうかは制御できないのです。
s8079

2020/02/08 12:25 編集

「引数を2つ以上必要とするstruct helperを書いてしまった時点でそのclass is_same_allは引数を2つ以上必要とする.だから,struct helperを書いていないclass is_same_allが必要だった.」という解釈で合っていますか.
SaitoAtsushi

2020/02/09 06:04

class is_same_all がどのような実装であることを想定しているのか私にはわからないので質問の意味が読み取れません。
s8079

2020/02/09 06:22

書き方が悪かったです. 「ヘルパ(structの部分)のテンプレート引数が2つ以上必要なとき,それを包むトレイト(classの部分)のテンプレート引数も2つ以上ないとテンプレートが展開できず,コンパイルエラーになる」ということが言いたかったのです. ・template<class U, class V, class... W> struct helper →最低U,Vの2つのテンプレート引数が必要 ・template<class ...T> class is_same_all →上記に従い,呼び出し時のテンプレート引数が2つ未満だとテンプレートを展開できず,コンパイルエラー
SaitoAtsushi

2020/02/09 06:59

ヘルパを呼び出す前に別の処理で上手いこと補ったり分岐したり出来る可能性はあるので「引数を2つ以上必要とするstruct helperを書いてしまった時点で」というというのは判断が早すぎると思いますが、クラステンプレートが受け取った型引数をそのままヘルパに渡すということなら当然ながら数が合わない (足りない) 場合にはエラーです。 helper の特殊化で場合分けを増やしてもよいですし、 is_same_all の特殊化でも対処可能です。 標準ライブラリの流儀では判定が真のときに std::true を、偽のときに std::false を継承する形式にしていて直接には value を定義していないので、それを真似るとするなら is_same_all を特殊化する形になると思います。
guest

0

SaitoAtsushi さんの回答の補足です。

回答にあるように、C++11 とその標準ライブラリの範囲内では処理の自作が必要です。ただし、基本的な処理は Boost C++ Libraries にある可能性があります。

質問の場合、Boost.MPL に、std::is_integral のような bool 値を返すメタ関数の論理積(&&)を計算するメタ関数として boost::mpl::and_、論理否定(!)を計算するメタ関数として、boost::mpl::not_ があります。これら使うと、次のように書けます。

cpp

1#include <iostream> 2#include <utility> 3#include <boost/mpl/and.hpp> 4#include <boost/mpl/not.hpp> 5 6// static_assert が常に評価されることを防ぐ 7template <typename... T> 8struct dependent_false { static constexpr bool value = false; }; 9 10// オーバーロードの優先順位の制御のため 11template <int I> struct rank : rank<I-1> {}; 12template <> struct rank<0> {}; 13 14template< 15 typename... T, 16 typename std::enable_if< 17 boost::mpl::and_<std::is_integral<T>...>::value, // すべて整数のとき true 18 std::nullptr_t 19 >::type = nullptr 20> 21void func_impl(rank<1>, T...) { 22 std::cout << "整数だよ" << std::endl; 23} 24 25template< 26 typename... T, 27 typename std::enable_if< 28 // すべて整数ではないとき true 29 boost::mpl::and_<boost::mpl::not_<std::is_integral<T>>...>::value, 30 std::nullptr_t 31 >::type = nullptr 32> 33void func_impl(rank<1>, T...) { 34 std::cout << "整数じゃないよ" << std::endl; 35} 36 37template<typename... T> 38void func_impl(rank<0>, T...) { 39 static_assert(dependent_false<T...>::value, "Error!"); 40} 41 42template <typename... T> 43void func(T&&... t) { 44 func_impl(rank<1>{}, std::forward<T>(t)...); 45}

補足:
C++17 では、<type_traits> に同様のメタ関数が用意されています。boost::mpl::and_ に対応するものとして std::conjunctionboost::mpl::not_ に対応するものとして std::negation があります。

投稿2020/02/01 08:55

編集2020/02/01 10:05
alphya

総合スコア124

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

SaitoAtsushi様と同じく(2)の例でエラーにはなりませんが、all_of(型版)のような汎用的なテンプレートを作成しライブラリ化しておくと、同じようなケースすべてに対して適応させることができます。

Cpp

1#include <iostream> 2#include <tuple> 3#include <type_traits> 4 5namespace detail { 6 template <template <class> class Pred, class List> 7 struct all_of_impl; 8 9 template <template <class> class Pred, 10 class Head, class... Tail> 11 struct all_of_impl<Pred, std::tuple<Head, Tail...>> { 12 static const bool value = 13 Pred<Head>::value ? 14 all_of_impl<Pred, std::tuple<Tail...>>::value : 15 false; 16 }; 17 18 template <template <class> class Pred> 19 struct all_of_impl<Pred, std::tuple<>> { 20 static const bool value = true; 21 }; 22} // namespace detail 23 24// all_of 25template <template <class> class Pred, class... List> 26struct all_of { 27 static const bool value = detail::all_of_impl<Pred, std::tuple<List...>>::value; 28}; 29 30// 目的の関数 31template <typename... T, 32 typename std::enable_if<all_of<std::is_integral, T...>::value, std::nullptr_t>::type = nullptr> 33void func(T... t) { 34 std::cout << "整数だよ" << std::endl; 35} 36 37template <typename... T, 38 typename std::enable_if<not all_of<std::is_integral, T...>::value, std::nullptr_t>::type = nullptr> 39void func(T... t) { 40 std::cout << "整数じゃないよ" << std::endl; 41} 42 43int main() { 44 func(10, 10); // 整数だよ(1) 45 func(10, 3.14); // 整数じゃないよ(2) 46 func(3.14, 3.14); // 整数じゃないよ(3) 47 48 return 0; 49}

投稿2020/02/01 17:09

asobinin

総合スコア69

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

こんにちは。

この辺は慣れですね。このくらいの難易度なら私でもなんとかなります。

C++

1#include <iostream> 2#include <utility> 3 4template<typename T, typename std::enable_if<std::is_integral<T>::value, std::nullptr_t>::type = nullptr> 5void func(T t) 6{ 7 std::cout << "整数だよ" << std::endl; 8} 9 10template<typename T, typename std::enable_if<!std::is_integral<T>::value, std::nullptr_t>::type = nullptr> 11void func(T t) 12{ 13 std::cout << "整数じゃないよ" << std::endl; 14} 15 16template<typename F, typename S, typename... T> 17void func(F f, S s, T... t) 18{ 19 static_assert(std::is_same<F, S>::value, "Error!"); 20 func(s, t...); 21} 22 23int main() { 24 func(10, 10); // 「整数だよ」を出力できるようにしたい. 25 func(10, 3.14); // 未定義動作.できればエラーにしたい. 26 func(3.14, 3.14); // 「整数じゃないよ」を出力. 27 return 0; 28}

wandbox

投稿2020/02/01 07:31

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

s8079

2020/02/01 08:21 編集

迅速な解答をありがとうございます. ただ,このコードはfuncを再帰してパラメータパックを分解しているように思います. 今回はパラメータパックをまとめて関数に渡したいのですが,どうにかならないでしょうか. 質問に説明を追記しておきます.
s8079

2020/02/01 08:25

あと,コンパイル時アサートは便利ですね. 大変勉強になりました.
Chironian

2020/02/01 08:42

なるほど。見落としていました。 パラメータパックを分解したくない理由にもよりますが、SaitoAtsushiさんの回答が近いと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問