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

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

ただいまの
回答率

87.38%

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

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,482

score 34

前提・実現したいこと

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

該当のソースコード

#include <iostream>
#include <utility>

template<typename... T, typename std::enable_if<std::is_integral<T...>::value, std::nullptr_t>::type = nullptr>
void func(T... t) {
    std::cout << "整数だよ" << std::endl;
}

template<typename... T>
void func(T... t) {
    std::cout << "整数じゃないよ" << std::endl;
}

int main() {
    func(10, 10); // 「整数だよ」を出力できるようにしたい.
    func(10, 3.14); // 未定義動作.できればエラーにしたい.
    func(3.14, 3.14); // 「整数じゃないよ」を出力.
    return 0;
}

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

C++11

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • SaitoAtsushi

    2020/02/01 15:11

    質問は何ですか?

    キャンセル

  • s8079

    2020/02/01 16:11

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

    キャンセル

回答 4

checkベストアンサー

+3

質問文中のコードは 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/09 15:04

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

    キャンセル

  • 2020/02/09 15: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つ未満だとテンプレートを展開できず,コンパイルエラー

    キャンセル

  • 2020/02/09 15:59

    ヘルパを呼び出す前に別の処理で上手いこと補ったり分岐したり出来る可能性はあるので「引数を2つ以上必要とするstruct helperを書いてしまった時点で」というというのは判断が早すぎると思いますが、クラステンプレートが受け取った型引数をそのままヘルパに渡すということなら当然ながら数が合わない (足りない) 場合にはエラーです。

    helper の特殊化で場合分けを増やしてもよいですし、 is_same_all の特殊化でも対処可能です。

    標準ライブラリの流儀では判定が真のときに std::true を、偽のときに std::false を継承する形式にしていて直接には value を定義していないので、それを真似るとするなら is_same_all を特殊化する形になると思います。

    キャンセル

+1

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

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

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

#include <iostream>
#include <utility>
#include <boost/mpl/and.hpp>
#include <boost/mpl/not.hpp>

// static_assert が常に評価されることを防ぐ
template <typename... T>
struct dependent_false { static constexpr bool value = false; };

// オーバーロードの優先順位の制御のため
template <int I> struct rank : rank<I-1> {};
template <> struct rank<0> {};

template<
    typename... T, 
    typename std::enable_if<
        boost::mpl::and_<std::is_integral<T>...>::value, // すべて整数のとき true
        std::nullptr_t
    >::type = nullptr
>
void func_impl(rank<1>, T...) {
    std::cout << "整数だよ" << std::endl;
}

template<
    typename... T,
    typename std::enable_if<
         // すべて整数ではないとき true
        boost::mpl::and_<boost::mpl::not_<std::is_integral<T>>...>::value,
        std::nullptr_t
    >::type = nullptr
>
void func_impl(rank<1>, T...) {
    std::cout << "整数じゃないよ" << std::endl;
}

template<typename... T>
void func_impl(rank<0>, T...) {
   static_assert(dependent_false<T...>::value, "Error!");
}

template <typename... T>
void func(T&&... t) {
    func_impl(rank<1>{}, std::forward<T>(t)...);
}

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

こんにちは。

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

#include <iostream>
#include <utility>

template<typename T, typename std::enable_if<std::is_integral<T>::value, std::nullptr_t>::type = nullptr>
void func(T t)
{
    std::cout << "整数だよ" << std::endl;
}

template<typename T, typename std::enable_if<!std::is_integral<T>::value, std::nullptr_t>::type = nullptr>
void func(T t)
{
    std::cout << "整数じゃないよ" << std::endl;
}

template<typename F, typename S, typename... T>
void func(F f, S s, T... t)
{
    static_assert(std::is_same<F, S>::value, "Error!");
    func(s, t...);
}

int main() {
    func(10, 10); // 「整数だよ」を出力できるようにしたい.
    func(10, 3.14); // 未定義動作.できればエラーにしたい.
    func(3.14, 3.14); // 「整数じゃないよ」を出力.
    return 0;
}


wandbox

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/02/01 17:13 編集

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

    キャンセル

  • 2020/02/01 17:25

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

    キャンセル

  • 2020/02/01 17:42

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

    キャンセル

0

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

#include <iostream>
#include <tuple>
#include <type_traits>

namespace detail {
    template <template <class> class Pred, class List>
    struct all_of_impl;

    template <template <class> class Pred,
        class Head, class... Tail>
        struct all_of_impl<Pred, std::tuple<Head, Tail...>> {
        static const bool value =
            Pred<Head>::value ?
            all_of_impl<Pred, std::tuple<Tail...>>::value :
            false;
    };

    template <template <class> class Pred>
    struct all_of_impl<Pred, std::tuple<>> {
        static const bool value = true;
    };
} // namespace detail

// all_of
template <template <class> class Pred, class... List>
struct all_of {
    static const bool value = detail::all_of_impl<Pred, std::tuple<List...>>::value;
};

// 目的の関数
template <typename... T, 
    typename std::enable_if<all_of<std::is_integral, T...>::value, std::nullptr_t>::type = nullptr>
void func(T... t) {
    std::cout << "整数だよ" << std::endl;
}

template <typename... T, 
    typename std::enable_if<not all_of<std::is_integral, T...>::value, std::nullptr_t>::type = nullptr>
void func(T... t) {
    std::cout << "整数じゃないよ" << std::endl;
}

int main() {
    func(10, 10);     // 整数だよ(1)
    func(10, 3.14);   // 整数じゃないよ(2)
    func(3.14, 3.14); // 整数じゃないよ(3)

    return 0;
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 87.38%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る