moveを阻害しないconstexprの文脈でのconstメンバ関数と非constメンバ関数の呼び分け

以下の記事と全く同じ事にハマった。

小ネタ - constexpr の文脈でconstメンバ関数と非constメンバ関数を呼び分ける
http://boleros.hateblo.jp/entry/20120331/1333187905

その際に、追加でハマった事を捕捉的に書いておく。

引数がrvalue referenceであった際に、そのままconst lvalue referenceにキャストしたgetメンバ関数の結果を返すと、アクセス先の値が実際にはconst修飾されていなくてもconst lvalue referenceが返る。その値をそのままmoveするとconst rvalue referenceが返るので、結果としてrvalue referenceを取るmoveコンストラクタは呼ばれず、多くの場合(const rvalue referenceを取る奇特なコンストラクタが無ければ)コピーコンストラクタが呼ばれることになり、効率の良いmoveを阻害する。const_castで元の型のlvalue referenceに戻してからmoveしてやると良さそう。

[追記]
こちらも参照
とくにあぶなくないRiSKのブログ - constexprとrvalue参照問題 http://sscrisk.hatenablog.com/entry/2014/05/31/constexpr%E3%81%A8rvalue%E5%8F%82%E7%85%A7%E5%95%8F%E9%A1%8C

以下サンプル

http://melpon.org/wandbox/permlink/zuEvu7XQEpQhdR0K

#include <type_traits>
#include <iostream>

template<typename T>
struct X {
    T t;
    T& get() {
        return t;
    }
    constexpr T const& get() const {
        return t;
    }
};

template<typename T>
struct Y {
    Y(const T&) { std::cout << "copy" << std::endl; }
    Y(T&&) { std::cout << "move" << std::endl; }
};

template<typename T>
constexpr auto get(X<T>&& t) -> decltype(std::move(const_cast<X<T> const&>(t).get())) {
    return std::move(const_cast<X<T> const&>(t).get());
}

template<typename T>
constexpr auto improved_get(X<T>&& t) -> T&& {
    return std::move(const_cast<T&>(const_cast<X<T> const&>(t).get()));
}

int main() {
    static_assert(std::is_same<
                    decltype(get( X<int>{1} )),
                    const int&&
                  >::value, "");
    Y<int> y1((get( X<int>{1} ))); // moveを阻害する
    
    static_assert(std::is_same<
                    decltype(improved_get( X<int>{1} )),
                    int&&
                  >::value, "");
    Y<int> y2(improved_get( X<int>{1} )); // moveを阻害しない
}