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

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

ただいまの
回答率

89.12%

コンテナ以外を対象とした関数を追加することによるSFINAE失敗

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 245
退会済みユーザー

退会済みユーザー

右辺値/左辺値/コンテナ/コンテナ以外を引数としてなんでも渡し標準出力する関数を作成しています。
コンテナ以外の値(int, doubleなど)を想定して作成した関数のオーバーロードを追加すると失敗してしまいます。
なぜ特定に失敗してしまうのでしょうか。またどのように修正すべきかご教示いただけると助かります。

#include <bits/stdc++.h>

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

template<typename... Ts>
struct is_container_helper {};

template<typename T>
struct is_container<
    T,
    std::conditional_t<
        false,
        is_container_helper<
            typename T::value_type,
            typename T::size_type,
            typename T::iterator,
            typename T::const_iterator,
            decltype(std::declval<T>().size()),
            decltype(std::declval<T>().begin()),
            decltype(std::declval<T>().end()),
            decltype(std::declval<T>().cbegin()),
            decltype(std::declval<T>().cend())
        >,
        void
    >
> : public std::true_type {};

template <typename T>
constexpr auto is_container_v = is_container<T>::value;

auto f(){}; 

// lvalueコンテナ用
template<typename Head, typename... Tail, std::enable_if_t<is_container_v<Head>, std::nullptr_t> = nullptr>
auto f(Head& head, Tail&&... tail) {
    for (auto& h : head) {
        if (&h != &head.back())  std::cout << h << " ";
        else std::cout << h << std::endl;
    }
    f(std::forward<Tail>(tail)...);
}

// rvalueコンテナ用
template<typename Head, typename... Tail, std::enable_if_t<is_container_v<Head>, std::nullptr_t>  = nullptr>
auto f(Head&& head, Tail&&... tail) {
    for (auto&& h : head) {
        if (&h != &head.back())  std::cout << h << " ";
        else std::cout << h << std::endl;
    }
    f(std::forward<Tail>(tail)...);
}

// コンテナ以外の値出力(のつもりだったができない)
// コメントを外すとSFINAE失敗する
/*
template <typename Head, typename... Tail, std::enable_if_t<!is_container_v<Head>, std::nullptr_t> = nullptr>
auto f(Head&& head, Tail&&... tail) {
    if (sizeof...(Tail)) std::cout << head << " ";
    else std::cout << head << std::endl;
    f(std::forward<Tail>(tail)...);
}
*/

int main() {
    // 左辺値array -> OK
    std::array<std::string, 3> arr{"first", "second", "third"};
    f(arr);

    // 左辺値vector複数 -> OK
    std::vector<int> vec{11, 13, 17, 19, 22};
    f(vec, vec);

    // 右辺値vector -> OK
    f(std::vector<int>{1, 2, 3});

    // 右辺値vector複数 -> OK
    f(std::vector<int>{11, 11, 11}, std::vector<int>{22, 22, 22});

    // 左辺値/右辺値/コンテナ/その他関係なく出力したい
    //f(vec, std::vector<int>{99, 99, 99}, 123);
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+3

こんにちは。

これはかなり複雑な話です。
auto f(Head&& head, 略)のHead&&はユニバーサル参照です。lvalue, rvalue共に受け取ることができます。
そして、落とし穴があります。
headに左辺値を渡すと、Headは参照となります。そしてis_container_v<T>はTにコンテナの参照が渡るとfalseになるようです。
その結果、lvalueコンテナ用と非コンテナ用(ユニバーサル参照)の両方にマッチするということのようです。

「lvalueコンテナ用」は混乱の元なので削除した方が良いと思います。
そして、is_container_v<T>へ渡す前にstd::remove_reference_t<T>で参照を外す、もしくは、is_container_v<T>自体を参照でも適切に判定出来るようにする等で対処できるだろうと思います。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/03/24 19:36

    回答ありがとうございます。
    おっしゃる通りstd::remove_reference_tで参照を外したところ判別ができました。lvalue用コンテナは不要のため削除することにします。
    落とし穴の記事拝読いたしました。知りませんでした…とてもためになりました。ありがとうございました。

    キャンセル

0

詳しい説明はここが分かりやすいです。

https://cpplover.blogspot.com/2011/04/c0xenableif.html

評価結果以前に「同じ型の関数がふたつあるよ!」という扱いになってしまうようです。

返却値で判定する方式にすると上手くいきます。 つまり、

template<typename Head, typename... Tail>
std::enable_if_t<is_container_v<Head>> f(Head& head, Tail&&... tail) {
// (省略)

といったような要領ですね。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/03/24 19:31 編集

    回答ありがとうございます。
    頂いたURLを元ネタにした[std::enable_ifを使ってオーバーロードする時、enablerを使う?](https://qiita.com/kazatsuyu/items/203584ef4cb8b9e52462)を参考にさせていただいているのでおそらくその点では大丈夫かなと思っています。
    Chironianさんの回答を参考にstd::remove_reference_tで参照を外したところうまく判別できました。今回の判定失敗はユニバーサル参照で左辺値を受け取ったことにより成功する関数がなくなっていたようです…

    キャンセル

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

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