C++11以降では、ユーザ定義変換演算子(user-defined conversion operator)の戻り値型をconst T
よりも非constなT
としたほうが良い。関数オーバーロードで引数型const T&
およびT&&
を受ける関数*1へ渡す際に、同関数呼び出し時のオーバーロード解決失敗によるコンパイルエラーを引き起こす。
本記事の内容はStack Overflowで見つけた質問と回答に基づく。Howard Hinnant氏の回答によれば、これはC++11/14言語仕様の問題でありCWG defect候補とのこと。
下記コードではstd::vector<B>::push_back
呼び出しにて型A
から型B
への暗黙型変換を意図しているが、C++標準コンテナのpush_back
メンバ関数ではconst B&
とB&&
のオーバーロードを提供するため、C++11/14標準規格に厳密準拠したコンパイラとライブラリでのみ問題が生じる。なお、rvalue参照型が存在しなかったC++03標準規格では問題とならない。
#include <vector> struct B { int m_; B(int v) : m_(v) {} }; struct A { int m_; A(int v) : m_(v) {} // A から const B への変換演算子 operator const B() const { return B(m_); } }; int main() { std::vector<B> v; v.push_back( A(42) ); // ?? }
gcc 4.9.1/libstdc++, Clang 3.5.0/libc++(-std=c++98), MSVC11(Visual Studio 2012)では正常にコンパイルできる。Clang 3.5.0/libc++(-std=c++11)では下記の通りコンパイルエラーとなる。これは期待される結果ではないが、C++11言語仕様準拠の振る舞いとなっている。(…らしい。詳細仕様は理解できていない。)
error: no viable conversion from 'A' to 'value_type' (aka 'B')
note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'A' to 'const B &' for 1st argument
note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'A' to 'B &&' for 1st argument
ユーザ定義変換演算子の戻り値型をconst B
からB
へ変更すると、Clang(libc++) C++11/14モードでも正常にコンパイルできるようになる。
struct A { int m_; A(int v) : m_(v) {} // `A`から`B`への変換演算子 operator B() const { return B(m_); } };
関連URL
- c++ - C++11 vs C++98 conversion operator behavior changes? - Stack Overflow
- 16682 – rvalue overload hides the const lvalue one?
- C++ Standard Core Language Defect Reports, #1604 Double temporaries in reference initialization
- 参照渡し or 値渡し? - yohhoyの日記