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

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

ただいまの
回答率

90.50%

  • C++

    3567questions

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

SFINAE できるコードとできないコードの違い

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 4
  • VIEW 1,651

Eki

score 318

C++ 初心者です。
先日、 SFINAE という仕様があると言うことを耳にしました。
面白がっていろいろと試していたのですが、途中で疑問に当たりました。

まず SFINAE について確認させてください。
私は、「テンプレートを型に置き換えるときに文法上のエラーがあった場合は、それをオーバーロード候補から外し(エラーとせず)続行」だと認識しています。
ここで型に置き換えるときのエラーとは、単純には例えば std::enable_if<false>::type などと言う型はない、のようなものだと思っています。
もし誤解があれば、お教えください。

ここで次のようなコードについて考えます。

Code 1:

extern void* enabler;
template <typename T, typename std::enable_if<std::is_same<T, int>::value>::type*& = enabler>
void foo(T) {}
template <typename T, typename std::enable_if<std::is_same<T, long>::value>::type*& = enabler>
void foo(T) {}

いわゆる enabler を使うコードです。
上のコードは T が int か long かによって正しく呼び分けられますよね。これは理解できました。

ところが

Code 2:

template <typename T, typename = typename std::enable_if<std::is_same<T, int>::value>::type>
void foo(T) {}
template <typename T, typename = typename std::enable_if<std::is_same<T, long>::value>::type>
void foo(T) {}

このようにするとコンパイラに怒られます。
しかしこのコードは T が int のとき、std::enable_if<std::is_same<int, long>::value>::type が存在しないので、下側の関数がオーバーロード候補から外されるのではと思うのですが、どういうことなのでしょうか。

コンパイラのエラーは

error: template parameter redefines default argument
template <typename T, typename = typename std::enable_if<std::is_same<T, long>::value>::type>

です(Clang 3.7.1)。
読む限りではきっとシグネチャ?が同じなのでオーバーロードできないということなのでしょうが、そうだと言うのなら enabler を用いた例が通るのが逆に不思議に思えてしまいます。

長くなりましたが、要するにお聞きしたいことは Code 1 が 許されて、 Code 2 が許されない理由です。

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

Clang (3.7.1), C++14

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+3

こんにちは。

「テンプレートを型に置き換えるときに文法上のエラーがあった場合は、それをオーバーロード候補から外し(エラーとせず)続行」

上記の通りに解釈すれば良さそうです。

Code 1の第2パラメータは「非型」パラメータです。void*&型(enable_if<true>時)か、型でさえ無い何か(enable_if<false>時)になります。
enable_if<>というクラス・テンプレートを型に置き換える時、falseだったら「型でさえ無い何か」になり、それは文法エラーになりますので、無かったことになります。

code 2は、単にtemplate<typename T, typename> void foo(T) {}を2つ並べたものですので、同じものを2つ定義したことになります。(デフォルト・パラメータより先に型が評価されるようです。)
ここが参考になります。(この江添氏のサイトは本当に有用です。ありがたや、ありがたや。)

なお、clangの場合、同じものの宣言にデフォルト引数を2回以上書くと、template parameter redefines default argumentエラーになるそうなので、そこでエラーになっているようです。


【追記】
そうそう、enablerについては、std::enable_ifを使ってオーバーロードする時、enablerを使う?も面白いです。

なんと、yumetodoさんがコメントされてますね。彼が江添氏へ確認したところ「いいのではないか。しかし、そろそろこういうことはコンセプトでやりたい。」って返事が返っているようですが、conceptの採用は延期されてしまいました。orz

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/04/12 23:37

    とてもわかりやすい回答ありがとうございます。

    なるほど確かに。よくよく考えてみれば、デフォルト引数は型情報自体には関係なくても自然ですね^^;
    評価順なども勉強していきます。
    腑に落ちました。

    はい、本当に「本の虫」にはお世話になってます。というかこれ自体、ここに「オーバーロードできない」と書かれていたことに疑問を持ったことから始まったのでした。

    キャンセル

  • 2016/04/13 00:05

    >そうそう、enablerについては、std::enable_ifを使ってオーバーロードする時、enablerを使う?も面白いです。

    あー、書こうと思ったら先を越されたでござる。

    >conceptの採用は延期

    あれ、それ確定でしたっけ、確かに
    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4567.pdf
    にはそれらしいものはありませんでしたが今もconcept回りの提案は続いているような(なお江添氏は今の提案はお気に召さない模様)

    キャンセル

  • 2016/04/13 00:38

    > ここに「オーバーロードできない」と書かれていたことに疑問を持ったことから始まったのでした。

    あ、なるほど。

    テンプレートは本当に難しいです。特に非型パラメータ、なかなかハードです。

    キャンセル

  • 2016/04/13 00:49

    あっとと、concept採用が見送られたのはC++11でした。既に古い話でした。ごめんなさい。
    時間切れで見送られただけで、いつか採用される筈と信じてます。

    でも、分かりにくいエラーを分かりやすくできるものより、もっと記述が容易なメタ・プログラミング文法を採用してくれるとよいのになとも思います。プリプロセッサをチューリング完全にしつつ、コンパイラと融合してくれないかな。

    キャンセル

  • 2016/04/13 00:55

    ネットで解説されているテンプレートやらメタプログラミングやらを見ると本当に心折れそうになるのですが、そういうポテンシャルや奥深さ(と複雑さ)を持っているC++だからこそ面白いなあと思えます。

    キャンセル

  • 2016/04/13 01:03

    C++ でもプリプロセッサの需要があるのですか。それは意外でした、テンプレート万能感がとてもあるので・・・

    ただ本当に初心者にとってはやっぱりエラーメッセージはわかりやすいほうが嬉しいですね。 STL 使ってみよう、と思ったときにちょっと間違えただけで恐ろしそうなエラーでますから・・・

    キャンセル

  • 2016/04/13 02:46

    テンプレートにはできなくて、プリプロセッサだからできることありますよ。
    (もちろん逆も多数あります。というか逆の方が多いと思います。)

    プリプロセッサについては、でちまるさんが第一人者?かも。
    BoostのBOOST_PP_FORを解説してます。→http://www.slideshare.net/digitalghost/c-35069539

    Boostは結構プリプロセッサを駆使してます。プリプロセッサ・ライブラリを提供しているだけでなくて、Boostの機能の実装にプリプロセッサを駆使してます。

    clangも意外な#includeの使い方してますし。foo.incファイルがちらほらありますが、#include "foo.inc"を複数の箇所に配置し、同じfoo.incを使って異なる定義群を生成してます。
    中々便利なテクニックですよ。

    > STL 使ってみよう、と思ったときにちょっと間違えただけで恐ろしそうなエラーでますから・・・

    ですね。msvcのメッセージの不親切さは酷いですし。
    gccのメッセージの量にめげそうになりますね。たった1つのエラーに対して、数KBytesのメッセージがマジででるんですもんね。それでもmsvcよりはかなり有用。
    clangはどうです? 良いと噂には聞くのですが。

    あ、そうそうMetashellご存知です?
    http://ig.hateblo.jp/entry/2015/12/01/204303
    clangを使ったテンプレートのインタプリタだそうです。コンパイル過程をステップ実行できるらしいです。

    キャンセル

  • 2016/04/13 19:35

    template書いているとtype数がboooostしてしまいがちで、
    using new_type = /*なんか*/;
    namespace ch = std::chrono;
    とかしてある程度type数を減らせる部分もありますが、できないところはマクロにしてしまいますね。これが最も身近なプリプロセッサマクロの使い方だろうか。

    コンパイルエラーの読みやすさはダントツでclangですが(そのためだけにClang with Microsoft CodeGen使ってます)gccもtemplate周りでは割りと役に立つ情報を投げてくれます。え?MSVCはどうだって?そんなもん知らんな(目をそらす)

    キャンセル

  • 2016/04/13 19:37

    >あっとと、concept採用が見送られたのはC++11でした。既に古い話でした。ごめんなさい。

    あやや、そっちの話だったか。

    >でも、分かりにくいエラーを分かりやすくできるものより、もっと記述が容易なメタ・プログラミング文法を採用してくれるとよいのになとも思います。

    とりあえずコンパイルエラーをbooostさせないためにconceptは入って欲しいんですが、確かにMS C#のT4みたいなのがほしいですね

    キャンセル

  • 2016/04/14 00:22

    Boost って C++ の黒魔術だと思ってましたが、プリプロセッサもフル活用なんですね。
    BOOST_PP_FOR の解説については、また時間のあるときに読ませてもらいます。それにしてもそれだけの規模のマクロを副作用なくコーディングできるのが恐ろしいです。

    Metashell 初めて聞きました。ちょっとだけ触ってみましたが、型表示機能など便利そうですね。今まで全部 typeid().name() をデマングルしてました。

    auto が入ったからでしょうか。 std::chrono は全部は打ちたくないです。
    なるほどここでマクロですか。確かに単純に置き換えるだけというのには便利ですね。未だに定数はマクロ使ってしまいます

    VisualStudio は起動が遅い&ちょっと試したいだけでも重量級&規格準拠度が・・・
    intelliSense は便利なんですけどね。

    concept については何かいろいろあったようですが、何が悪いのかわかりやすくなる機能は入ってほしいものです。作る側もわかりやすくなって良い気がしますし。

    キャンセル

  • 2016/04/14 00:35 編集

    >定数はマクロ使ってしまいます
    constexprは市民の義務

    >規格準拠度
    最近は頑張ってるんやで!VS2015Update1でC++11constexpr(99%)対応、VS2015Update2とかいう微妙なタイミングでC++14のVariable Templates対応しましたし。_MSC_FULL_VERマクロが大活躍しますね!

    >intelliSense は便利
    なおコンパイラ班の作業速度にIntelliSense班の作業速度が追いついていない模様。まあIntelliSenseの事実上の作りなおしみたいなことしてたみたいなのでやむを得ないですが、Clang with Microsoft CodeGenつかうと``__has_include_next``マクロが認識できないせいでエラー千超えとか普通で使いものにならないし、Variable Templatesも使うとエラーが爆発するし、コンパイラは対応してないのにIntelliSenseは
    http://cpplover.blogspot.jp/2014/12/c14.html
    N4200に対応してるものだから、間違って機能テストマクロ使っちゃうし(ものすごい罠)

    >Boost って C++ の黒魔術だと思ってましたが
    もっと身近なところにも黒魔術はあるで。C#では引数の型名の前にthisと書くだけのメソッドチェーンもC++にかかればご覧の有様よ!
    https://gist.github.com/yumetodo/936010a8ab2370e71368

    キャンセル

  • 2016/04/14 01:16

    > constexpr は市民の義務
    はい。がんばります。

    > 最近は頑張ってる
    へー、そう聞くとたしかにけっこう。そうだったのか・・・
    私の場合は新しい機能で簡単そうなのをちょこちょこ触るのが好きで、あまり実用的なことはしてない(できない)せいもあるのですが、規格を満たしてくる clang がお気に入りです。

    > IntelliSense班の作業速度が追いついていない模様
    IDE の一番のメリットが。でもコンパイラと IntelliSense って別々に開発されてたんですね(無知
    コンパイラが対応していないのを補完候補に出すのってどうなんだ・・・

    > 身近なところにも黒魔術
    うわあ・・・すみませんまだ読めるレベルじゃないです・・・
    使うシーンを見るとすごくシンプルなのに、それを実現するのにあれだけかかってしまうんですね。
    それもそれとして C++ の演算子オーバーロードって本当にいろいろな部分で有用ですね。初めて C++ を見たときは cout の << にびっくりしましたけど。

    キャンセル

  • 2016/04/14 02:01 編集

    >規格を満たしてくる clang

    バグもおおい(相互再帰バグとか未だに治らない)

    >うわあ・・・すみませんまだ読めるレベルじゃないです・・・
    C++erならありきたりなコードやで(多分、きっと、おそらく)
    まあ
    - 演算子の優先順位
    - operaotrのオーバーロード
    - templateの部分特殊化
    - SFINAE
    - 戻り値を後値する記法
    - Argumets Dependent Look-up
    は知らないといけませんが。
    operator|はクラスメンバー関数もどきを作るのに重宝しますね。クラスメンバー関数もどきの引数の型がmoveできなくてコピーのコストが高い時はこれよりもっと複雑な実装になるらしいですが、それは私も理解していない

    まあしかしC#みたいなExtension Methodsとかoperator.のオーバーロードとか欲しいですね(後者は提案されてたけど)

    キャンセル

  • 2016/04/14 21:53

    相互再帰バグ、聞いたことあります。
    だいぶ前に聞いたような気がしますが、残ってたのですか。

    頑張ってすごく大雑把にですけど読んでみました。

    1. Split によってデリミタ情報を持つ detail::Split_helper が生成される
    2a. detail::Split_helper::operator[] によって、デリミタとユーザーが欲するカラム番号を持つ detail::Split_helper_index が生成される
    3a. std::basic_string と detail::Split_helper_index についての operator| のオーバーロードで、文字列をうまいこと分割して求めるカラムを返す

    2b. std::basic_string と detail::Split_helper についての operator| のオーバーロードで、文字列をうまいこと分解して
    3b. それを std::vector<std::basic_string> に入れて返す
    4b. 受け取り側がその std::vector を [] でアクセス

    と言う感じでしょうか?
    でも肝心の分割部分についてはよくわかりませんでした。

    operator. はメンバアクセスとややこしいから、と書いてあるのを見ました。
    C# はよく知りませんが、いろいろな機能が入っているのですね。

    コメント欄の内容がだんだんとずれてきた気がしますが、いいのでしょうか(質問掲示板初心者でもあります)
    でも、いろいろと勉強になります。ありがとうございます。

    キャンセル

  • 2016/07/30 03:31

    そういえば例に上げた文字列分割はvector格納前に変換関数に掛けられるようにして、ちゃんとテストケースも書いたりしました。ついでに解説記事も書きました。
    http://qiita.com/yumetodo/items/bf2bc5c1d49d5aec3efa
    https://github.com/yumetodo/string_split

    conceptに関してはほぼ確実にC++17には入らないようです

    キャンセル

0

デフォルト引数に指定したものが違ったとしてもシグネチャがおなじになるということだと思うんだけど、規格書でtypenameとオーバーロード周りどうなってたかな・・・。

追記

いやそうじゃないな。two-phase name look up 絡みだな。多分、typename std::enable_if</*略*/>::typeを見る時、先頭のtypename から何らかの型なんだなと評価し
typename = typename /*略*/と認識する。でそうなるとシグネチャがどっちも同じと認識する、みたいな感じだと思う。ただtwo-phase name look upをざっと調べた感じどっちもdependent nameだと思うんだけど・・・あれ・・・?

推測なのでちょっと後で調べます。

追記2

>デフォルト・パラメータより先に型が評価されるようです。

それだ!。さて問題はこれ、規格書のどこに該当するかだな。

というかそのサイト私も何回も見てるはずなのになんで思い出せなかったかなぁ・・・。

追記3

コメントでもあるように確かにN4296やN4567には

A template-parameter shall not be given default arguments by two different declarations in the same scope.
[ Example:

template<class T = int> class X;
template<class T = int> class X { /... / }; // error


—end example ]

という記載がありますね。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/04/12 23:29

    回答ありがとうございます。

    なるほど、とりあえずいったん typename 以下を放っておいて処理する段階で同じ形とみなされエラーになるということですか。分かった気がします。
    two-phase name look up も(聞いたことあるレベルで)理解していないので、また勉強します。
    やはり理解しようとすればしっかり文法を学んでいくことが大切ですね。

    キャンセル

  • 2016/04/12 23:31

    いや、どっちもdependent nameのはずだからtwo-phase name look up先生は関係なさそう(多分)、いずれにせよ規格書をもう一回読まねば・・・

    キャンセル

  • 2016/04/12 23:34

    >>デフォルト・パラメータより先に型が評価されるようです。
    >それだ!。さて問題はこれ、規格書のどこに該当するかだな。

    ですね。江添氏もサラッとスルーしているし...

    キャンセル

  • 2016/04/13 00:08

    いっそask.fmで江添氏に直接凸するか・・・

    キャンセル

  • 2016/04/13 13:15

    "先に評価"というより http://stackoverflow.com/questions/14197436/c11-template-parameter-redefines-default-argument で回答されているC++11 [temp.param]/12が直接根拠では?
    `template<typename T, typename X>`の形で、Xにデフォルトテンプレート引数を渡す異なる関数テンプレート宣言が存在するため(型パラメータXに与えるデフォルト型が異なっていますが、そもそも前述項目に反しています)。

    キャンセル

  • 2016/04/14 00:25

    結局はオーバーロードしたつもりが同じ宣言になっちゃってるということですね。

    キャンセル

  • 2016/04/14 02:16

    うわ、ホントだ。どうもです

    キャンセル

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

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

関連した質問

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

  • C++

    3567questions

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