
std::reference_wrapper
배열은 어떤 객체 타입의 element든 가질 수 있다 (기본 타입, 복합 타입)
int a{ 1 };
int b{ 2 };
std::array valarr{ a, b }; //std::array<int, 2>
std::vector ptrarr{ &a, &b }; //std::vector<int*>
이때 참조는 객체가 아니기 때문에 참조로 이루어진 배열을 만들 수 없다, 또한 배열의 element는 값 변경(할당)이 가능해야 한데 참조는 한번 초기화 되면 다른 대상을 가리키도록 재지정될 수 없다
int a{ 1 };
int b{ 2 };
std::array<int&, 2> refArr{ a }; //compile error
하지만 참조 타입 변수로 초기화 하는건 가능하다 (단순히 참조한 값으로 배열의 element를 구성함)
int a{ 1 };
int b{ 2 };
int& refa{ a };
int& refb{ b };
std::array arr{ a, b }; //ok
위 내용들은 std::array뿐 아니라 C-style 배열, std::vector에 전부 적용된다
그렇다면 참조 타입의 element를 가지는 배열을 만들려면 어떻게 해야할까?
이럴때 바로 < functional > 헤더에 정의된 표준 라이브러리 클래스 템플릿인 std::reference_wrapper를 사용해야 한다
타입 T를 받아서 T에 대한 수정 가능한 lvalue 참조처럼 동작시켜준다
std::reference_wrapper는 참조와 같은 동작을 하지만 실제로는 참조를 감싸는 객체이기 때문에 배열에서 사용이 가능하다
int a{ 1 };
int b{ 2 };
std::array<std::reference_wrapper<int>, 2> arr{ a, b };
//std::reference_wrapper<>로 a객체 참조를 감싸는 객체를 만든다
arr[0].get() = 10; //get()으로 실제 참조를 얻어와 값을 수정했기 때문에 a의 값이 변경됨
std::cout << a;
get()으로 실제 참조(T&)를 얻을 수 있다, 실제 참조 대상의 값을 변경할 때 많이 사용한다
std::reference_wrapper< T >는 T&로 암시적 변환이 가능하다, 따라서
std::cout << arr[0] << std::endl;
위 코드에서 컴파일러는 std::reference_wrapper< int > 를 int&로 암시적 변환하고 값을 출력한다
std::reference_wrapper< >는 operator=를 이용하여 참조하는 대상을 변경할 수 있다 (일반 참조는 재지정이 불가능하지만 std::reference_wrapper는 가능함)
int a{ 1 };
int b{ 2 };
std::reference_wrapper<int> tempref{ a };
tempref = b; //가리키는 대상이 a -> b로 변경됨
tempref.get() = 200;
std::cout << a << std::endl; //1
std::cout << b << std::endl; //200
C++17 이전에는 CTAD기능이 없었기 때문에 클래스 템플릿 타입 추론이 불가능했다, 따라서 std::reference_wrapper< T > 를 전부 명시적으로 작성해야 했다
하지만 이제는 타입은 CTAD로 추론이 가능하기 때문에 따로 작성하지 않아도 된다
int a{ 1 };
int b{ 2 };
std::reference_wrapper tempref{ a };
auto tempref1{ std::reference_wrapper{ b } };
C++11부터 std::reference_wrapper라는 긴 이름과 템플릿 인수를 명시적으로 적어야 하는 번거로움을 해결하기 위한 함수인 std::ref()와 std::cref()가 제공되었다
이 두 함수는 std::reference_wrapper, std::reference_wrapper< const > 객체를 생성하는 shortcut이라고 보면 된다


cref()로 만든 std::reference_wrapper 객체는 get()으로 수정이 불가능한걸 확인할 수 있다
enumerator를 이용한 std::array
[ ]는 bound check를 하지 않기 때문에 초기화 된 element의 index보다 높은 index로 접근 시 크래시가 발생하게 된다
이를 std::vector와 같은 방식으로 enumerator를 사용하여 보호가 가능하다
enum Count
{
a,
b,
c,
d,
maxcount
};
int main()
{
constexpr std::array fooarr{ 1, 2, 3 };
fooarr[d]; //crash, out of bound!
return 0;
}
static_assert()를 이용하여 검사하고 인덱싱하는게 out of bound방지에 좋다

이전에 정리한 string_view를 받아 enumerator값을 return하는 함수, enumerator를 받아 enumerator의 이름을 출력하는 함수를 std::array를 이용하여 더 깔끔하게 구현할 수 있다
string_view를 받아 enumerator값을 return하는 함수와 enumerator를 받아 enuemrator의 이름을 출력하는 함수는 다음과 같다
constexpr std::string_view GetEnumName(Colors InColor)
{
switch (InColor)
{
case Colors::Red:
return "Red";
case Colors::Green:
return "Green";
case Colors::Blue:
return "Blue";
default:
return "Unknown";
}
}
constexpr std::optional<Colors> GetEnumFromString(std::string_view InStringView)
{
if (InStringView == "Red")
{
return Colors::Red;
}
else if (InStringView == "Green")
{
return Colors::Green;
}
else if (InStringView == "Blue")
{
return Colors::Blue;
}
return std::nullopt;
}
이렇게 함수를 구현하게 되면 enum의 데이터가 추가되고 삭제될 때 마다 switch와 if 수정이 들어가야 한다는 것이다 (유지보수성 저하)
이를 배열을 사용하여 조금 더 유지보수성이 좋게 구현이 가능하다
#include <array>
#include <iostream>
#include <string_view> // std::string_view 사용
#include <cstddef> //std::size_t
#include <string> // std::getline
namespace MyNamespace
{
enum Colors
{
Red,
Green,
Blue,
MaxCount
};
using namespace std::string_view_literals; //sv suffix를 사용하기 위함
constexpr std::array ColorNames = { "Red"sv, "Green"sv, "Blue"sv };
static_assert(ColorNames.size() == MaxCount, "ColorNames size must be equal to MaxCount");
}
constexpr std::string_view GetColorName(MyNamespace::Colors InColor)
{
return MyNamespace::ColorNames[static_cast<std::size_t>(InColor)];
}
std::ostream& operator<<(std::ostream& OutStream, MyNamespace::Colors color) {
// getColorName 함수를 사용하여 색상 이름을 얻고 출력 스트림으로 보내기
return OutStream << GetColorName(color);
}
std::istream& operator>>(std::istream& InStream, MyNamespace::Colors& OutColor)
{
std::string ColorName{};
std::getline(InStream >> std::ws, ColorName); // std::ws는 공백을 무시함
for (std::size_t i = 0; i < MyNamespace::MaxCount; ++i)
{
if (ColorName == MyNamespace::ColorNames[i])
{
OutColor = static_cast<MyNamespace::Colors>(i);
return InStream;
}
}
InStream.setstate(std::ios_base::failbit);
return InStream;
}
int main()
{
MyNamespace::Colors color{ MyNamespace::Colors::Red };
std::cout << color << std::endl;
std::cout << "Enter a new color: ";
std::cin >> color; // 사용자 입력을 받기 위해 operator>> 오버로딩 사용
if (!std::cin) // 스트림 상태 확인 (failbit가 설정되었는지)
{
std::cout << "Invalid input\n"; // 입력 실패 시
}
else
{
std::cout << "Your color is " << color << '\n'; // 입력 성공 시
}
return 0;
}
for loop로 enumerator 순회하기
for (int i = 0; i < MyNamespace::MaxCount; ++i)
{
std::cout << MyNamespace::Colors(i) << '\n';
}
단순한 방법으로 enumerator를 순회하면 정수값으로 출력이 된다, 이를 문자열로 출력하려면 instream, outstream을 위와 같이 오버로딩해야 한다
namespace MyNamespace
{
enum Colors
{
Red,
Green,
Blue,
MaxCount
};
using namespace std::string_view_literals; //sv suffix를 사용하기 위함
constexpr std::array ColorNames = { "Red"sv, "Green"sv, "Blue"sv };
static_assert(ColorNames.size() == MaxCount, "ColorNames size must be equal to MaxCount");
}
constexpr std::string_view GetColorName(MyNamespace::Colors InColor)
{
return MyNamespace::ColorNames[static_cast<std::size_t>(InColor)];
}
std::ostream& operator<<(std::ostream& OutStream, MyNamespace::Colors color) {
// getColorName 함수를 사용하여 색상 이름을 얻고 출력 스트림으로 보냅니다.
return OutStream << GetColorName(color);
}
std::istream& operator>>(std::istream& InStream, MyNamespace::Colors& OutColor)
{
std::string ColorName{};
std::getline(InStream >> std::ws, ColorName); // std::ws는 공백을 무시함
for (std::size_t i = 0; i < MyNamespace::MaxCount; ++i)
{
if (ColorName == MyNamespace::ColorNames[i])
{
OutColor = static_cast<MyNamespace::Colors>(i);
return InStream;
}
}
InStream.setstate(std::ios_base::failbit);
return InStream;
}
int main()
{
for (int i = 0; i < MyNamespace::MaxCount; ++i)
{
std::cout << static_cast<MyNamespace::Colors>(i) << '\n';
}
}
for-each로 enumerator를 순회하는건 허용되지 않는다

for-each loop는 begin(), end()와 같은 멤버함수나 이러한 역할을 하는 함수를 가지는 컨테이너 타입에서만 작동한다, enum은 해당 조건을 만족하지 못하기 때문에 for-each loop 사용이 불가능하다
배열에서는 for-each loop가 동작하기 때문에 배열로 대체해서 사용하면 된다
namespace MyNamespace
{
enum Colors
{
Red,
Green,
Blue,
MaxCount
};
constexpr std::array CollorArr{ Red, Green, Blue };
static_assert(CollorArr.size() == MaxCount, "CollorArr size must be equal to MaxCount");
}
for (auto color : MyNamespace::CollorArr)
{
std::cout << color << '\n';
}
//string_view로 출력하고 싶다면 위의 >>, << 연산자 오버로딩이 필요함