const lvalue reference constructorとuniversal reference in constructorの共存における問題の解決法
帰ったらブログにでも書くよと言ってしまったので書く...
例題として、TypeErasureを利用した簡単なAnyを示す。こういったTypeErasureにはテンプレートコンストラクタを必要とするが該当部分でuniversal reference constructorを用いてPerfect Forwardingを実現している。
#include<utility> #include<memory> struct Any final{ template<class _Tp> Any(_Tp&& x) :ptr(new Erasure<_Tp>(std::forward<_Tp>(x))) {} Any(const Any& any) :ptr(any.ptr.get()?any.ptr->clone():nullptr) {} private: struct ErasureBase{ virtual ~ErasureBase()=default; virtual ErasureBase* clone()=0; }; template<class _Tp> struct Erasure:ErasureBase{ _Tp data; explicit Erasure(const _Tp& x) :data(x) {} ErasureBase* clone()override{return new Erasure(data);} }; std::shared_ptr<ErasureBase> ptr=nullptr; template<class _Tp> friend _Tp get(const Any&); }; template<class _Tp> _Tp get(const Any& any)noexcept{return std::dynamic_pointer_cast<Any::Erasure<_Tp>>(any.ptr)->data;} #include<iostream> int main() { Any a=10; Any b=a; std::cout<<get<int>(a)<<std::endl<<get<int>(b)<<std::endl; }
しかしこのコード、コンパイルは通るが実行するとsegmentation faultする。なぜか。
コピーコンストラクタをdeleteしてみよう。
Any(const Any& any)=delete;
a.cpp: In instantiation of ‘Any::Erasure<_Tp>::Erasure(const _Tp&) [with _Tp = Any]’: a.cpp:7:8: required from ‘Any::Any(_Tp&&) [with _Tp = Any]’ a.cpp:35:8: required from here a.cpp:21:11: エラー: use of deleted function ‘Any::Any(const Any&)’ :data(x) ^ a.cpp:9:2: 備考: ここで宣言されています Any(const Any& any)=delete; ^~~
なんと、_TpがAny型に推論されている上、コピーコンストラクタが呼ばれずにuniversal reference in constructorが割り込んでいるではないか。問題を更に簡略化してみれば以下の通り。
#include<iostream> template<class _Tp> void f(_Tp&&){std::cout<<"template :"<<__func__<<std::endl;} void f(const int&){std::cout<<"non-template :"<<__func__<<std::endl;} int main() { f(42); }
これはどちらが呼ばれるだろうか。
template :f
テンプレート版が呼ばれてしまった...というところまでが今回のお題。
これを回避する方法。
SFINAE発動
#include<iostream> #include<utility> template<class _Tp> void f(_Tp&&, std::void_t< typename std::enable_if<!std::is_same<int,typename std::decay<_Tp>::type>::value>::type>* =nullptr) { std::cout<<"template:"<<__func__<<std::endl; } void f(const int&){std::cout<<"non-template:"<<__func__<<std::endl;} int main() { f(10); f("hoge"); }
non-template:f template:f
Anyにも同じく適用する。
struct Any{ template<class _Tp> Any(_Tp&& x, std::void_t< typename std::enable_if<!std::is_same<Any,typename std::decay<_Tp>::type>::value>::type >* =nullptr) :ptr(new holder<_Tp>(std::forward<_Tp>(x))) {} // ...
結論
Effective Modern C++を読むべき。