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

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

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

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

Q&A

解決済

2回答

7016閲覧

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

Eki

総合スコア429

C++

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

5グッド

4クリップ

投稿2016/04/12 13:00

編集2016/04/12 16:31

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

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

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

Code 1:

C++

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

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

ところが

Code 2:

C++

1template <typename T, typename = typename std::enable_if<std::is_same<T, int>::value>::type> 2void foo(T) {} 3template <typename T, typename = typename std::enable_if<std::is_same<T, long>::value>::type> 4void 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

Chironian, ikuwow, argius, yohhoy👍を押しています

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

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

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

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

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

guest

回答2

0

ベストアンサー

こんにちは。

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

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

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 14:23

編集2016/04/12 14:30
Chironian

総合スコア23272

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

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

Eki

2016/04/12 14:37

とてもわかりやすい回答ありがとうございます。 なるほど確かに。よくよく考えてみれば、デフォルト引数は型情報自体には関係なくても自然ですね^^; 評価順なども勉強していきます。 腑に落ちました。 はい、本当に「本の虫」にはお世話になってます。というかこれ自体、ここに「オーバーロードできない」と書かれていたことに疑問を持ったことから始まったのでした。
yumetodo

2016/04/12 15:05

>そうそう、enablerについては、std::enable_ifを使ってオーバーロードする時、enablerを使う?も面白いです。 あー、書こうと思ったら先を越されたでござる。 >conceptの採用は延期 あれ、それ確定でしたっけ、確かに http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4567.pdf にはそれらしいものはありませんでしたが今もconcept回りの提案は続いているような(なお江添氏は今の提案はお気に召さない模様)
Chironian

2016/04/12 15:38

> ここに「オーバーロードできない」と書かれていたことに疑問を持ったことから始まったのでした。 あ、なるほど。 テンプレートは本当に難しいです。特に非型パラメータ、なかなかハードです。
Chironian

2016/04/12 15:49

あっとと、concept採用が見送られたのはC++11でした。既に古い話でした。ごめんなさい。 時間切れで見送られただけで、いつか採用される筈と信じてます。 でも、分かりにくいエラーを分かりやすくできるものより、もっと記述が容易なメタ・プログラミング文法を採用してくれるとよいのになとも思います。プリプロセッサをチューリング完全にしつつ、コンパイラと融合してくれないかな。
Eki

2016/04/12 15:55

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

2016/04/12 16:03

C++ でもプリプロセッサの需要があるのですか。それは意外でした、テンプレート万能感がとてもあるので・・・ ただ本当に初心者にとってはやっぱりエラーメッセージはわかりやすいほうが嬉しいですね。 STL 使ってみよう、と思ったときにちょっと間違えただけで恐ろしそうなエラーでますから・・・
Chironian

2016/04/12 17: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を使ったテンプレートのインタプリタだそうです。コンパイル過程をステップ実行できるらしいです。
yumetodo

2016/04/13 10:35

template書いているとtype数がboooostしてしまいがちで、 using new_type = /*なんか*/; namespace ch = std::chrono; とかしてある程度type数を減らせる部分もありますが、できないところはマクロにしてしまいますね。これが最も身近なプリプロセッサマクロの使い方だろうか。 コンパイルエラーの読みやすさはダントツでclangですが(そのためだけにClang with Microsoft CodeGen使ってます)gccもtemplate周りでは割りと役に立つ情報を投げてくれます。え?MSVCはどうだって?そんなもん知らんな(目をそらす)
yumetodo

2016/04/13 10:37

>あっとと、concept採用が見送られたのはC++11でした。既に古い話でした。ごめんなさい。 あやや、そっちの話だったか。 >でも、分かりにくいエラーを分かりやすくできるものより、もっと記述が容易なメタ・プログラミング文法を採用してくれるとよいのになとも思います。 とりあえずコンパイルエラーをbooostさせないためにconceptは入って欲しいんですが、確かにMS C#のT4みたいなのがほしいですね
Eki

2016/04/13 15:22

Boost って C++ の黒魔術だと思ってましたが、プリプロセッサもフル活用なんですね。 BOOST_PP_FOR の解説については、また時間のあるときに読ませてもらいます。それにしてもそれだけの規模のマクロを副作用なくコーディングできるのが恐ろしいです。 Metashell 初めて聞きました。ちょっとだけ触ってみましたが、型表示機能など便利そうですね。今まで全部 typeid().name() をデマングルしてました。 auto が入ったからでしょうか。 std::chrono は全部は打ちたくないです。 なるほどここでマクロですか。確かに単純に置き換えるだけというのには便利ですね。未だに定数はマクロ使ってしまいます VisualStudio は起動が遅い&ちょっと試したいだけでも重量級&規格準拠度が・・・ intelliSense は便利なんですけどね。 concept については何かいろいろあったようですが、何が悪いのかわかりやすくなる機能は入ってほしいものです。作る側もわかりやすくなって良い気がしますし。
yumetodo

2016/04/13 15:39 編集

>定数はマクロ使ってしまいます 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
Eki

2016/04/13 16:16

> constexpr は市民の義務 はい。がんばります。 > 最近は頑張ってる へー、そう聞くとたしかにけっこう。そうだったのか・・・ 私の場合は新しい機能で簡単そうなのをちょこちょこ触るのが好きで、あまり実用的なことはしてない(できない)せいもあるのですが、規格を満たしてくる clang がお気に入りです。 > IntelliSense班の作業速度が追いついていない模様 IDE の一番のメリットが。でもコンパイラと IntelliSense って別々に開発されてたんですね(無知 コンパイラが対応していないのを補完候補に出すのってどうなんだ・・・ > 身近なところにも黒魔術 うわあ・・・すみませんまだ読めるレベルじゃないです・・・ 使うシーンを見るとすごくシンプルなのに、それを実現するのにあれだけかかってしまうんですね。 それもそれとして C++ の演算子オーバーロードって本当にいろいろな部分で有用ですね。初めて C++ を見たときは cout の << にびっくりしましたけど。
yumetodo

2016/04/13 17:09 編集

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

2016/04/14 12: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# はよく知りませんが、いろいろな機能が入っているのですね。 コメント欄の内容がだんだんとずれてきた気がしますが、いいのでしょうか(質問掲示板初心者でもあります) でも、いろいろと勉強になります。ありがとうございます。
guest

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:

cpp

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

—end example ]

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

投稿2016/04/12 14:06

編集2016/04/13 17:14
yumetodo

総合スコア5850

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

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

Eki

2016/04/12 14:29

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

2016/04/12 14:31

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

2016/04/12 14:34

>>デフォルト・パラメータより先に型が評価されるようです。 >それだ!。さて問題はこれ、規格書のどこに該当するかだな。 ですね。江添氏もサラッとスルーしているし...
yumetodo

2016/04/12 15:08

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

2016/04/13 04:15

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

2016/04/13 15:25

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

2016/04/13 17:16

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問