Rokiのチラ裏

学生による学習のログ

stack overflow からの小ネタメモ #2

前回

stackoverflow.com 質問者は、void_t detection idiom によるSFINAEを試行している。void_t には、確かにテンプレート型に依存するパラメータが渡されているが、C++14 N3936 [temp.alias] を参照すると、テンプレートパラメータのエイリアステンプレートに対して型を指定した時、それが未使用だった場合、テンプレート引数の置換が保証されるような文面がないのである。

template<class...> // 無名のテンプレートパラメータ
using void_t = void;

// value_type を持つ事を要求する
// C++14 では template-idが例え依存していたとしてもテンプレート引数の置換は義務ではない
template<class T,void_t<T::value_type>* = nullptr>
void f(T){}

解決法としては、使用すれば良いわけなので、以下のように make_voidなどを用意してやる。

template<typename...> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

しかしこのような動作は、直感的ではないという事で、CWG Issue 1980として取り上げられている。これによれば、14.5.7 [temp.alias] に対して drafting されているとの事で、執筆時現在では、[temp.alias] にて確認できる。内容を引用。

However, if the template-id is dependent, subsequent template argument substitution still > applies to the template-id. [ Example: template<typename…> using void_t = void; template void_t f(); f(); // error, int does not have a nested type foo — end example ]

template-id が依存している場合は、それ以降のテンプレート引数の置換は引き続き template-id に適用される。つまり、これは先ほど述べた内容を保証するといった文面であるから、質問者のコードは、間違いなく C++1z 以降の処理系では正しく動作するべきである。そして、 C++14 では、void_t の内部実装を上記のような void_make などのようなやり方にして、C++1z と同じように動くべきである。

stackoverflow.com GCC と Clang で、それぞれstd::arrayのサイズを0として宣言する際に、default-constructible を要求するかが異なるといった質問だ。これは、[array.zero] にその動作が定義されている。ただ、その文面には、0サイズのstd::arrayをどのように実装すべきかについて執筆時現在言及されていない。つまり IDB なので、実装が default-constructible を要求したとしても、規格違反とはならないのである。しかし、これは規定すべきだという事で LWG issue 2157 として取り上げられている。LWG 2157 では、配列の不特定の内部構造は0サイズのstd::arrayの初期化を許可し、任意の型Tが default-constructible でない場合でも、有効でなければならないといった文面が提示されている。

stackoverflow.com 質問内容は、ローカルクラスがテンプレートメンバーを持つことができない明確な理由とは?といったもの。
回答内容から一部引用する。

template <typename T>
class X {
    template <typename>
    void foo() {
        T t;
    }
};

このクラス X に対して void を指定しても foo を呼び出さなければそれは ill-formed ではない。何故ならば、呼び出されなければ、あるいは明示的インスタンス化がされなければ*1インスタンス化されないからだ。続いて、さらに回答内容から一部引用。

template <typename T>
auto bar() {
    return [] (auto) {T t;};
};

このコードは ill-formed であるが、これを機能としてサポートする場合を考える。この場合、ローカルコンテキスト*2に依存しないように、ローカルのテンプレートを十分にインスタンス化する必要があるため、generic lambda の body 部分をテンプレート関数 bar のテンプレート引数 T を使って部分的にインスタンス化しなければならない。
テンプレート関数は先ほども述べた通りインスタンス化の遅延が施されるが、ジェネリックラムダも所謂テンプレート版関数オブジェクト、つまりローカルテンプレートクラスである。しかし、回答でも触れられている通り、特にテンプレートメンバ関数がテンプレート関数のローカルクラスの内部にある場合コンパイラ側の対応がとても困難である*3ため、このような部分的インスタンス化を行うような機能が導入されなかったのだとのこと。…しかし、ラムダの body 部分にテンプレートパラメータの型を書くという事は、割と自然にやってしまいそうなものだ。一応、CWG issue 728として取り上げられたようだが、extention となったようだ。CWG issue 728 で問いている内容に対する答えは、上記の通り処理系側の実装困難な問題であるというのが最も妥当だろう。実際、GCC はこのこの extention をサポートしている?ようだが当然 IDB なので、やはり推奨されるものでは決してないだろう。

stackoverflow.com C++にはデフォルト引数を再定義する機能がある([dcl.fct.default]/4)。この機能を有効的に使うシーンは何かあるだろうか?という内容。これに対して、既存のコードやライブラリを変更できず、実際に正しい引数を入力することができない場合、一部のスコープのデフォルト引数を変更することが、いくつかのレガシーツールで生成されたC++コードを扱うときに役立つハックのようなものとして利用できるとのアンサーが1つ付いている。例えば、生成されたコードがデフォルトの引数を使用して何百もの外部ライブラリに何百もの呼び出しを持っていたが、デフォルトの引数がもう正しくない場合など。確かに、そういう用途では検討しても良いのかもしれないが、混沌を更に深めるような気もしてくる。正直、デフォルト引数そのものがそこまで良いものとも思えないので、わざわざ積極的に利用するものでもないだろう。

*1:コンパイラオプション等による明示的インスタンス化を含む

*2:それを含む関数テンプレートのテンプレートパラメータを含む

*3:処理系の実装として、関数定義の処理中に使用される語彙スコープが基本的に一時的であることが知られているとのこと。