Type Safe typedef 사용하기
2020-04-28, Tue
C++에서 typedef 또는 using을 이용해 타입에 새로운 이름을 붙여줄 수 있다.
using myint1 = int;
typedef int myint2;
이 타입들끼리는 원하지 않더라도 서로 연산이 가능하다.
myint1 i1{1};
myint2 i2{2};
i1 = i2;
std::cout << i1 + i2 << "\n";
std::cout << i1 << "\n";
여기서부턴 취향에 따라 갈리겠지만, 타입에 더 많은 정보를 담고 싶다면 간단한 트릭을 통해 새 타입들을 구분할 수 있다.
template <class T, class>
struct strong_type
{
T v;
explicit strong_type(T&& s) : v(std::move(s)) {}
strong_type& operator=(T&& s)
{
v = s;
return *this;
}
explicit operator const T&() const
{
return v;
}
};
using myint3 = strong_type<int, struct myint3_tag>;
using myint4 = strong_type<int, struct myint4_tag>;
myint3 i3{3};
myint4 i4{4};
i3 = i4; // 컴파일 에러!
std::cout << i3 << "\n"; // 컴파일 에러!
std::cout << static_cast<int>(i3) << "\n";
std::cout << i3 + i4 << "\n"; // 컴파일 에러!
원래 이럴 때 쓰라고 BOOST_STRONG_TYPEDEF가 있는데, 탬플릿 대신 매크로를 사용한 구현이다.
typedef와 string typedef는 하스켈에서 type
과 newtype
같은 관계이다.
type MyInt = Int
newtype MyIntNew = MyIntNew Int
print $ (1:: MyInt) + (2:: MyInt)
print $ MyIntNew 1 + MyIntNew 2 -- 컴파일 에러!
위 방법대로 쓰기 이전에 사용하던 방법인데, compile time string과 non type template paramter(NTTP)를 쓰면 다른 방식으로도 구현이 가능하다. 레딧에서 찾은 방법인데, 현 시점 최신인 clang10에선 안되고 gcc9에선 되지만 이렇게까지 할 필요는 없을 듯. 자체적으로 구현하는 FixedString
말고 std::string
이 constexpr해지면 그 때는 해볼법도 싶다.
#include <iostream>
template <size_t N>
struct FixedString
{
char buf[N]{};
constexpr FixedString(char const* s)
{
// std::copy_n(s, N - 1, buf); // GCC9.3에선 작동하지 않는다. trunk에선 잘 됨.
for (size_t i = 0; i < N - 1; ++i)
buf[i] = s[i];
}
};
template <size_t N>
FixedString(const char (&)[N]) -> FixedString<N>;
template <class T, FixedString>
struct strong_type
{
T v;
explicit strong_type(T&& s) : v(std::move(s)) {}
strong_type& operator=(T&& s)
{
v = s;
return *this;
}
explicit operator const T&() const
{
return v;
}
};
using myint1 = strong_type<int, "myint1">;
using myint2 = strong_type<int, "myint2">;
int main()
{
myint1 i1{1};
myint2 i2{2};
i1 = i2; // 컴파일 에러!
std::cout << i1 + i2 << "\n"; // 컴파일 에러!
std::cout << static_cast<int>(i1) << "\n";
}