constexpr 関数から引数のメンバ関数を呼ぶための forward
この記事で取り上げた問題について。
小ネタ - constexpr の文脈でconstメンバ関数と非constメンバ関数を呼び分ける - ボレロ村上 - ENiyGmaA Code
簡単に言えば constexpr 関数では、
引数が rvalue reference の場合、constメンバ関数が呼ばれてほしいわけだ。
なので、統一的な書き方が出来るようにする。
#include <iostream> #include <sprout/string.hpp> #include <sprout/utility.hpp> typedef sprout::string<32> string_t; struct X { constexpr string_t call() const { return sprout::to_string("called const version"); } string_t call() { return sprout::to_string("called non-const version"); } }; template<typename T> constexpr string_t call(T&& t) { return sprout::lvalue_forward<T>(t).call(); } int main() { { // non-const lvalue を渡す X x; auto s = call(x); std::cout << s << std::endl; } { // const lvalue を渡す constexpr X x; constexpr auto s = call(x); std::cout << s << std::endl; } { // rvalue を渡す constexpr auto s = call(X{}); std::cout << s << std::endl; } }
-
- 出力
called non-const version called const version called const version
rvalue を渡したときに、きちんと const 版が呼ばれていることがわかる。
ポイントはここ。
return sprout::lvalue_forward<T>(t).call();
sprout::lvalue_forward の実装は以下。
-
- sprout/utility/lvalue_forward.hpp
namespace sprout { // // lvalue_forward // template<typename T> inline SPROUT_CONSTEXPR typename sprout::lvalue_reference<T>::type lvalue_forward(typename std::remove_reference<T>::type& t) { return sprout::as_lvalue(sprout::forward<T>(t)); } template<typename T> inline SPROUT_CONSTEXPR typename sprout::lvalue_reference<T>::type lvalue_forward(typename std::remove_reference<T>::type&& t) = delete; } // namespace sprout
-
- sprout/utility/as_lvalue.hpp
namespace sprout { // // as_lvalue // template<typename T> inline T& as_lvalue(T& t) { return t; } template<typename T> inline SPROUT_CONSTEXPR T const& as_lvalue(T const& t) { return t; } } // namespace sprout
実装はいたって簡単で、ようは lvalue reference に暗黙の変換をしているだけ。
T&& は T& でなく T const& に変換されるため、めでたくconstメンバ関数が呼ばれるわけです。
とりあえず T&& で引数を取る constexpr 関数から引数のメンバ関数を呼ぶ場合、
forward でなく lvalue_forward を使うようにすれば概ね問題ないでしょう(おそらく)。