と http://cpplover.blogspot.jp/2013/04/isexplicitlyconvertible.html のコメント書いてて思ったので,書く.
まず些細な違いとして, auto
は右辺の値の const
や参照を消し飛ばす*1,という点が挙げられる.
using T = int const; // Alias declarations; I prefer this to typedef double x = 1.2; T t(x); // decltype(t) is int const auto t = T(x); // const is removed; decltype(t) is int
using T = int const&; int x = 42; T t(x); // t is a const reference to x; decltype(t) is int const& auto t = T(x); // x is copied; decltype(t) is int
が,これは今回の記事では これ以上は言及しない.
詳しく知りたい方は http://d.hatena.ne.jp/gintenlabo/20110223/1298482597 を参照.
真に問題なのは, T がポインタの場合である:
using T = int*; double d; auto x = &d; T t1(x); // ill-formed auto t2 = T(x); // well-formed(!), because T(x) is C-style cast
上記のコードで, auto t2 = T(x);
はコンパイルエラーとはならない.
なぜなら T(x)
という式は, (T)x
同じ C-style cast だからだ.
そして,少しでも C++ を知っている人であれば あえて言うまでもない事実だが, C-style cast は邪悪である.
故に, T(x)
という表現もまた,邪悪である.
良心ある C++ 使いを自覚するなら,決して使ってはならない.
* * * * *
さて,この挙動は, Variadic Templates を使う場合,特に問題になる.
例えば
template<class T, class... Args> T create(Args&&... args) { return T( std::forward<Args>(args)... ); }
このような関数を作ったとき,
double d; auto p = create<int*>(&d);
この式は問題なくコンパイルが通ってしまう上,多くのコンパイラでは警告すら出ない.
この挙動は,間違いなく危険である. よって,上記の create
の定義は,面倒でも
template<class T, class... Args> T create(Args&&... args) { T t( std::forward<Args>(args)... ); return t; // t is implicitly moved (or copy is omitted by NRVO) }
と書くのが良い.((厳密に言うと T
が const 修飾されてたり参照だったりする時には上手く行かないので,そのようなケースには別個 対応する必要がある))
もしくは, uniform initialization を使って
return T{ std::forward<Args>(args)... };
と書く手もある.
ただ,これは T
が参照の場合には上手くいかないし,
{} による初期化は () による初期化とは微妙に挙動が違うので,厳密には同じ動作にはならないことは留意するべきである.
(が,細かいことを気にしないならば {} で統一するのは有力である. 少なくとも大怪我はしない.)
また,上記の create
を SFINAE に対応させる場合,
template<class T, class... Args> auto create(Args&&... args) -> decltype( T( std::forward<Args>(args)... ) );
と書くと問題になるので,じゃあ一体どうすればいいのか,と思うかもしれないが,*2
標準には まさにその T t( std::forward
の可非を判定するために
std::is_constructible
というメタ関数が存在しているため,
template<class T, class... Args, typename std::enable_if< std::is_constructible<T, Args...>::value >::type* = 0 > T create(Args&&... args) { return T( std::forward<Args>(args)... ); }
と書けば良い.
副次的な利点として,こう書けば不正なケースは SFINAE で除外されるため,
上記のように 直接 T( std::forward
を返すことで, constexpr
にも対応できるようになる.