ボレロ村上 - ENiyGmaA Code

中3女子です。

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 を使うようにすれば概ね問題ないでしょう(おそらく)。