utility_forward_like

utility_forward_like (std::forward_like)

이 페이지는 C++23에서 도입된 std::forward_like 함수 템플릿을 설명해요. std::forward_like는 주어진 객체의 값 카테고리와 const 한정을 유지하면서 멤버나 원소에 접근할 수 있도록 도와주는 캐스트예요. 주로 "먼 객체(far object)"를 적응시키는 시나리오에서 유용하게 사용돼요.

출처: cppreference

본문

<utility> 헤더에 정의되어 있어요.

Defined in header <utility>
template < class T , class U > constexpr auto && forward_like ( U && x ) noexcept ; (since C++23)

T&&와 유사한 속성을 가진 x에 대한 참조를 반환해요.

반환 타입은 다음과 같이 결정돼요:

  • std::remove_reference_t<T>가 const 한정 타입이라면, 반환 타입의 참조 대상 타입은 const std::remove_reference_t<U>예요. 그렇지 않으면 참조 대상 타입은 std::remove_reference_t<U>예요.
  • T가 lvalue 참조 타입이라면 반환 타입도 lvalue 참조 타입이에요. 그렇지 않으면 반환 타입은 rvalue 참조 타입이에요.
  • T가 참조 가능한 타입이 아닌 경우(즉, cv 한정 void 또는 cv 한정자 시퀀스나 ref 한정자가 있는 함수 타입), 프로그램은 ill-formed예요.

매개변수

x - 타입 T처럼 전달되어야 하는 값

반환값

위에서 결정된 타입의 x에 대한 참조예요.

참고 사항

std::forward, std::move, std::as_const와 마찬가지로 std::forward_like는 표현식의 값 카테고리에 영향을 주거나 const 한정을 추가할 수 있는 타입 캐스트예요.

m이 실제 멤버이고 o.m이 유효한 표현식이라면, C++20 코드에서는 보통 std::forward<decltype(o)>(o).m으로 작성해요.

이로 인해 merge, tuple, language라는 세 가지 가능한 모델이 생겨요.

  • merge: const 한정자를 병합하고 소유자(Owner)의 값 카테고리를 채택해요.
  • tuple: Ownerstd::tuple<Member>라고 가정할 때 std::get<0>(Owner)가 수행하는 동작이에요.
  • language: std::forward<decltype(Owner)>(o).m이 수행하는 동작이에요.

std::forward_like가 주로 다루는 시나리오는 "먼 객체"를 적응시키는 것이에요. tuple 모델이나 language 모델 모두 이 주요 사용 사례에 적합하지 않기 때문에, std::forward_like에는 merge 모델이 사용돼요.

Feature-test macro 표준 기능
__cpp_lib_forward_like 202207L (C++23) std::forward_like

가능한 구현

template < class T , class U > constexpr auto && forward_like ( U && x ) noexcept { constexpr bool is_adding_const = std :: is_const_v < std :: remove_reference_t < T >> ; if constexpr ( std :: is_lvalue_reference_v < T &&> ) { if constexpr ( is_adding_const ) return std :: as_const ( x ); else return static_cast < U &> ( x ); } else { if constexpr ( is_adding_const ) return std :: move ( std :: as_const ( x )); else return std :: move ( x ); } }

예제

#include <cstddef>
#include <iostream>
#include <memory>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>

struct TypeTeller
{
    void operator()(this auto&& self)
    {
        using SelfType = decltype(self);
        using UnrefSelfType = std::remove_reference_t<SelfType>;
        if constexpr (std::is_lvalue_reference_v<SelfType>)
        {
            if constexpr (std::is_const_v<UnrefSelfType>)
                std::cout << "const lvalue\n";
            else
                std::cout << "mutable lvalue\n";
        }
        else
        {
            if constexpr (std::is_const_v<UnrefSelfType>)
                std::cout << "const rvalue\n";
            else
                std::cout << "mutable rvalue\n";
        }
    }
};

struct FarStates
{
    std::unique_ptr<TypeTeller> ptr;
    std::optional<TypeTeller> opt;
    std::vector<TypeTeller> container;
    
    auto&& from_opt(this auto&& self)
    {
        return std::forward_like<decltype(self)>(self.opt.value());
        // It is OK to use std::forward<decltype(self)>(self).opt.value(),
        // because std::optional provides suitable accessors.
    }
    
    auto&& operator[](this auto&& self, std::size_t i)
    {
        return std::forward_like<decltype(self)>(self.container.at(i));
        // It is not so good to use std::forward<decltype(self)>(self)[i], because
        // containers do not provide rvalue subscript access, although they could.
    }
    
    auto&& from_ptr(this auto&& self)
    {
        if (!self.ptr)
            throw std::bad_optional_access{};
        return std::forward_like<decltype(self)>(*self.ptr);
        // It is not good to use *std::forward<decltype(self)>(self).ptr, because
        // std::unique_ptr<TypeTeller> always dereferences to a non-const lvalue.
    }
};

int main()
{
    FarStates my_state
    {
        .ptr{std::make_unique<TypeTeller>()},
        .opt{std::in_place, TypeTeller{}},
        .container{std::vector<TypeTeller>(1)},
    };
    
    my_state.from_ptr()();
    my_state.from_opt()();
    my_state[0]();

    std::cout << '\n';
    
    std::as_const(my_state).from_ptr()();
    std::as_const(my_state).from_opt()();
    std::as_const(my_state)[0]();
    
    std::cout << '\n';
    
    std::move(my_state).from_ptr()();
    std::move(my_state).from_opt()();
    std::move(my_state)[0]();
    
    std::cout << '\n';
    
    std::move(std::as_const(my_state)).from_ptr()();
    std::move(std::as_const(my_state)).from_opt()();
    std::move(std::as_const(my_state))[0]();
    
    std::cout << '\n';
}

출력:

mutable lvalue
mutable lvalue
mutable lvalue

const lvalue
const lvalue
const lvalue

mutable rvalue
mutable rvalue
mutable rvalue

const rvalue
const rvalue
const rvalue

같이 보기

move (C++11) 인수를 xvalue로 변환해요 (함수 템플릿) [edit]
forward (C++11) 함수 인수를 전달하고 타입 템플릿 인수를 사용해 값 카테고리를 보존해요 (함수 템플릿) [edit]
as_const (C++17) 인수에 대한 const 참조를 얻어요 (함수 템플릿) [edit]

더 알아보기 (Learn more)

cppreference