와와

씹어먹는 C++ 공부하기 (9~) 본문

개발/C++

씹어먹는 C++ 공부하기 (9~)

정으주 2024. 8. 4. 12:17

 

9-1. 코드를 찍어내는 틀 - C++ 템플릿(template)

 

C++ 템플릿 (template)

class Vector {
  std::string* data;
  int capacity;
  int length;

 public:
  // 생성자
  Vector(int n = 1) : data(new std::string[n]), capacity(n), length(0) {}

  // 맨 뒤에 새로운 원소를 추가한다.
  void push_back(std::string s) {
    if (capacity <= length) {
      std::string* temp = new std::string[capacity * 2];
      for (int i = 0; i < length; i++) {
        temp[i] = data[i];
      }

      delete[] data;
      data = temp;
      capacity *= 2;
    }

    data[length] = s;
    length++;
  }

  // 임의의 위치의 원소에 접근한다.
  std::string operator[](int i) { return data[i]; }

  // x 번째 위치한 원소를 제거한다.
  void remove(int x) {
    for (int i = x + 1; i < length; i++) {
      data[i - 1] = data[i];
    }
    length--;
  }

  // 현재 벡터의 크기를 구한다.
  int size() { return length; }

  ~Vector() {
    if (data) {
      delete[] data;
    }
  }
};

 

과 같이 String 데이터를 저장하는 Vector 클래스가 있다.

String 말고도 int, char 등의 데이터를 저장하는데도 쓰고 싶다면 템플릿을 사용하면 됨.

T 라는 타입의 객체들을 보관하는 Vector 클래스는 다음과 같다.

 

// 템플릿 첫 활용
#include <iostream>
#include <string>

template <typename T>    //아래에 정의되는 클래스에 대해 템플릿을 정의
class Vector {
  T* data;
  int capacity;
  int length;

 public:
  // 생성자
  Vector(int n = 1) : data(new T[n]), capacity(n), length(0) {}

  // 맨 뒤에 새로운 원소를 추가한다.
  void push_back(T s) {
    if (capacity <= length) {
      T* temp = new T[capacity * 2];
      for (int i = 0; i < length; i++) {
        temp[i] = data[i];
      }
      delete[] data;
      data = temp;
      capacity *= 2;
    }

    data[length] = s;
    length++;
  }

  // 임의의 위치의 원소에 접근한다.
  T operator[](int i) { return data[i]; }

  // x 번째 위치한 원소를 제거한다.
  void remove(int x) {
    for (int i = x + 1; i < length; i++) {
      data[i - 1] = data[i];
    }
    length--;
  }

  // 현재 벡터의 크기를 구한다.
  int size() { return length; }

  ~Vector() {
    if (data) {
      delete[] data;
    }
  }
};

 

이런식으로 원하는 자료형으로 객체를 생성해주면 된다

 

Vector<int> int_vec;
Vector<string> string_vec;

 

 

템플릿 특수화

 

template <typename A, typename B, typename C>
class test {};

 

이렇게 클래스 템플릿이 정의되어 있을 때

특정 자료형의 경우에 객체를 따로 처리하고 싶을 수 있다.

그럴 땐

 

template <typename C>    //A = int, B = string, C=C 인 경우
class test<int, string, C> {};

template <>    //int 타입을 전달받은 경우
class test<int> {};

 

이렇게 지정해주면 된다.

이게 가능하다면

다음과 같이 bool 타입을 넘겨 받을 때 비트 연산을 통해 메모리 효율을 높힐 수 있음

 

template <>
class Vector<bool> {
  unsigned int* data;
  int capacity;
  int length;

 public:
  typedef bool value_type;

  // 생성자
  Vector(int n = 1)
      : data(new unsigned int[n / 32 + 1]), capacity(n / 32 + 1), length(0) {
    for (int i = 0; i < capacity; i++) {
      data[i] = 0;
    }
  }

  // 맨 뒤에 새로운 원소를 추가한다.
  void push_back(bool s) {
    if (capacity * 32 <= length) {
      unsigned int* temp = new unsigned int[capacity * 2];
      for (int i = 0; i < capacity; i++) {
        temp[i] = data[i];
      }
      for (int i = capacity; i < 2 * capacity; i++) {
        temp[i] = 0;
      }

      delete[] data;
      data = temp;
      capacity *= 2;
    }

    if (s) {
      data[length / 32] |= (1 << (length % 32));
    }

    length++;
  }

  // 임의의 위치의 원소에 접근한다.
  bool operator[](int i) { return (data[i / 32] & (1 << (i % 32))) != 0; }

  // x 번째 위치한 원소를 제거한다.
  void remove(int x) {
    for (int i = x + 1; i < length; i++) {
      int prev = i - 1;
      int curr = i;

      // 만일 curr 위치에 있는 비트가 1 이라면
      // prev 위치에 있는 비트를 1 로 만든다.
      if (data[curr / 32] & (1 << (curr % 32))) {
        data[prev / 32] |= (1 << (prev % 32));
      }
      // 아니면 prev 위치에 있는 비트를 0 으로 지운다.
      else {
        unsigned int all_ones_except_prev = 0xFFFFFFFF;
        all_ones_except_prev ^= (1 << (prev % 32));
        data[prev / 32] &= all_ones_except_prev;
      }
    }
    length--;
  }

  // 현재 벡터의 크기를 구한다.
  int size() { return length; }
  ~Vector() {
    if (data) {
      delete[] data;
    }
  }
};

 

함수 템플릿 (Function template)

 

template <typename Cont>
void bubble_sort(Cont& cont) {
  for (int i = 0; i < cont.size(); i++) {
    for (int j = i + 1; j < cont.size(); j++) {
      if (cont[i] > cont[j]) {
        cont.swap(i, j);
      }
    }
  }
}

 

클래스 템플릿처럼 함수 템플릿도 만들 수 있음

 

함수 객체 (Functor)

 

bubble_sort 함수에서 오름차순, 내림차순 정렬을 가능하게 해보자.

template <typename Cont, typename Comp>

void bubble_sort(Cont& cont, Comp& comp) {
  for (int i = 0; i < cont.size(); i++) {
    for (int j = i + 1; j < cont.size(); j++) {
      if (!comp(cont[i], cont[j])) {
        cont.swap(i, j);
      }
    }
  }
}

 

struct Comp1 {
  bool operator()(int a, int b) { return a > b; }
};

struct Comp2 {
  bool operator()(int a, int b) { return a < b; }
};

 

Comp 라는 클래스를 템플릿 인자로 받고, 함수 자체도 Comp 객체를 따로 받는다.

if 문에서 마치 함수를 호출하는 것 처럼 사용되는데, cont[i] 와 cont[j] 를 받아서 내부적으로 크기 비교를 수행한 뒤에 그 결과를 리턴함. comp 는 함수가 아니라 객체 이고, Comp 클래스에서 () 연산자를 오버로딩한 버전

Comp1 과 Comp2 객체들은 bubble_sort 함수 안에서 마치 함수인양 사용되는데

 

if (!comp(cont[i], cont[j])) {

 

이렇게, 함수는 아니지만 함수 인 척을 하는 객체를 함수 객체 (Function Object), 혹은 줄여서 Functor 라고 한다.

 

타입이 아닌 템플릿 인자 (non-type template arguments)

#include <iostream>

template <typename T, int num>
T add_num(T t) {
  return t + num;
}

int main() {
  int x = 3;
  std::cout << "x : " << add_num<int, 5>(x) << std::endl;
}

 

이처럼 템플릿 인자로 타입이 아닌 것들을 전달할 수 있다.

#include <iostream>
#include <array>

template <typename T>
void print_array(const T& arr) {
  for (int i = 0; i < arr.size(); i++) {
    std::cout << arr[i] << " ";
  }
  std::cout << std::endl;
}

int main() {
  std::array<int, 5> arr = {1, 2, 3, 4, 5};
  std::array<int, 7> arr2 = {1, 2, 3, 4, 5, 6, 7};
  std::array<int, 3> arr3 = {1, 2, 3};

  print_array(arr);
  print_array(arr2);
  print_array(arr3);
}

 

디폴트 템플릿 인자

#include <iostream>
#include <string>

template <typename T>
struct Compare {
  bool operator()(const T& a, const T& b) const { return a < b; }
};

template <typename T, typename Comp = Compare<T>>
T Min(T a, T b) {
  Comp comp;
  if (comp(a, b)) {
    return a;
  }
  return b;
}

int main() {
  int a = 3, b = 5;
  std::cout << "Min " << a << " , " << b << " :: " << Min(a, b) << std::endl;

  std::string s1 = "abc", s2 = "def";
  std::cout << "Min " << s1 << " , " << s2 << " :: " << Min(s1, s2)
            << std::endl;
}
= (디폴트 값)

 

으로 인자의 디폴트 값을 정해줄 수 있다.

 

 

9-2. 가변 길이 템플릿 (Variadic template)

 

가변 길이 템플릿 (variadic template)

 

임의의 개수와 타입의 인자를 받을 수 있는 템플릿을 만드는 기능 ( C++11 부터 도입 )

 

  • 템플릿 파라미터 팩(parameter pack)
typename... Types

 

  • 함수 파라미터 팩
Types... args

 

  • 문법
template <typename T, typename... Types>
void function(T first, Types... rest) {
    // 함수 본문
}

 

예제

template <typename T>
void print(T arg) {
  std::cout << arg << std::endl;
}

template <typename T, typename... Types>
void print(T arg, Types... args) {
  std::cout << arg << ", ";
  print(args...);
}

 

여기서

print(1, 3.1, "abc")

 

이러한 형태로 print를 한다고 하면

아래와 같은 형태의 print 가 재귀적으로 호출된다.

 

void print(int arg, double arg2, const char* arg3) {
  std::cout << arg << ", ";
  print(arg2, arg3);
}

void print(double arg, const char* arg2) {
  std::cout << arg << ", ";
  print(arg2);
}

void print(const char* arg) {
  std::cout << arg << std::endl;
}

 

문자열 더하기

 

아래와 같은 문자열 더하기는 더할 때마다 메모리 할당이 발생한다.

concat = s1 + s2 + s3;
concat = s1.operator+(s2).operator+(s3);

 

가변 길이 템플릿을 이용해 문자열의 길이를 먼저 구해 메모리를 할당하고, 문자열을 붙여보자!

 

1. 문자열 길이 구하기

//문자열 길이 구하기
size_t GetStringSize(const char* s) { return strlen(s); }

size_t GetStringSize(const std::string& s) { return s.size(); }

template <typename String, typename... Strings>
size_t GetStringSize(const String& s, Strings... strs) {
  return GetStringSize(s) + GetStringSize(strs...);
}

 

2. StrCat

template <typename String, typename... Strings>
std::string StrCat(const String& s, Strings... strs) {
  // 먼저 합쳐질 문자열의 총 길이를 구한다.
  size_t total_size = GetStringSize(s, strs...);

  // reserve 를 통해 미리 공간을 할당해 놓는다.
  std::string concat_str;
  concat_str.reserve(total_size);

  concat_str = s;

  // concat_str 에 문자열들을 붙인다.
  AppendToString(&concat_str, strs...);

  return concat_str;
}

 

3. 문자열 붙이기

void AppendToString(std::string* concat_str) { return; }

template <typename String, typename... Strings>
void AppendToString(std::string* concat_str, const String& s, Strings... strs) {
  concat_str->append(s);
  AppendToString(concat_str, strs...);
}

 

평균 구하기

#include <iostream>

// 재귀 호출 종료를 위한 베이스 케이스
int sum_all() { return 0; }

template <typename... Ints>
int sum_all(int num, Ints... nums) {
  return num + sum_all(nums...);
}

template <typename... Ints>
double average(Ints... nums) {
  return static_cast<double>(sum_all(nums...)) / sizeof...(nums);
}

int main() {
  // (1 + 4 + 2 + 3 + 10) / 5
  std::cout << average(1, 4, 2, 3, 10) << std::endl;
}

 

sizeof는 인자의 크기를 리턴

size of... 는 인자의 개수를 리턴

 

Fold Expression

 

C++11에서 도입된 가변 길이 템플릿은 매우 편리하지만 재귀 함수 형태로 구성해야 하기 때문에, 반드시 재귀 호출 종료를 위한 함수를 따로 만들어야 한다.

 

// 재귀 호출 종료를 위한 베이스 케이스
int sum_all() { return 0; }

 

이것처럼.

하지만 C++17에 새로 도입된 Fold 형식을 사용한다면 이를 훨씬 간단하게 표현할 수 있다.

 

#include <iostream>

template <typename... Ints>
int sum_all(Ints... nums) {
  return (... + nums);    // 이렇게!
}

int main() {
  // 1 + 4 + 2 + 3 + 10
  std::cout << sum_all(1, 4, 2, 3, 10) << std::endl;
}

 

코드를 보면

 

return (... + nums);    // 단항 좌측 Fold (Unary left fold)

 

위와 같은 부분을 컴파일러에서는 아래와 같이 해석한다.

return ((((1 + 4) + 2) + 3) + 10);

 

다양한 Fold 방식

,

연산자를 이용하여 각각의 인자들에 대해 원하는 식을 실행할 수 있다.

 

#include <iostream>

class A {
 public:
  void do_something(int x) const {
    std::cout << "Do something with " << x << std::endl;
  }
};

template <typename T, typename... Ints>
void do_many_things(const T& t, Ints... nums) {
  // 각각의 인자들에 대해 do_something 함수들을 호출한다.
  (t.do_something(nums), ...);
}
int main() {
  A a;
  do_many_things(a, 1, 3, 2, 4);
}

 

 

 

9-3. 템플릿 메타 프로그래밍 (Template Meta programming)

 

 

타입을 가지고 컴파일 타임에 생성되는 코드로 프로그래밍을 하는 것을 메타 프로그래밍(meta programming) 이라고 한다. C++ 의 경우 템플릿을 가지고 이러한 작업을 하기 때문에 템플릿 메타 프로그래밍, 줄여서 TMP 라고 부른다.

 

TMP 를 이용하는 경우는 꽤나 제한적이지만, 많은 C++ 라이브러리들이 TMP 를 이용해서 구현되었다 (Boost 라이브러리). TMP 를 통해서 컴파일 타임에 여러 오류들을 잡아낼 수 도 있고 (Ex. 단위나 통화 일치 여부등등) 속도가 매우 중요한 프로그램의 경우 TMP 를 통해서 런타임 속도도 향상 시킬 수 있다.

 

 

팩토리얼 계산

#include <iostream>
#include <typeinfo>

template <int N>
struct Int {
  static const int num = N;
};

template <typename T, typename U>
struct add {
  typedef Int<T::num + U::num> result;
};

int main() {
  typedef Int<1> one;	//타입 생성
  typedef Int<2> two;

  typedef add<one, two>::result three;

  std::cout << "Addtion result : " << three::num << std::endl;
}

 

 

 

최대 공약수 구하기

 

- 일반적인 함수

int gcd(int a, int b) {
  if (b == 0) {
    return a;
  }

  return gcd(b, a % b);
}

 

 

- TMP

#include <iostream>

template <int X, int Y>
struct GCD {
  static const int value = GCD<Y, X % Y>::value;
};

template <int X>
struct GCD<X, 0> {
  static const int value = X;
};

int main() {
  std::cout << "gcd (36, 24) :: " << GCD<36, 24>::value << std::endl;
}

 

 

 

Ratio : 유리수를 오차없이 표현해 주는 클래스

#include <iostream>
#include <typeinfo>


template <int X, int Y>
struct GCD {
  static const int value = GCD<Y, X % Y>::value;
};

template <int X>
struct GCD<X, 0> {
  static const int value = X;
};

template <int N, int D = 1>
struct Ratio {
  typedef Ratio<N, D> type;
  static const int num = N;  // 분자
  static const int den = D;  // 분모
};
template <class R1, class R2>
struct _Ratio_add {
  typedef Ratio<R1::num * R2::den + R2::num * R1::den, R1::den * R2::den> type;
};

template <class R1, class R2>
struct Ratio_add : _Ratio_add<R1, R2>::type {};

int main() {
  typedef Ratio<2, 3> rat;
  typedef Ratio<3, 2> rat2;
  typedef Ratio_add<rat, rat2> rat3;

  std::cout << rat3::num << " / " << rat3::den << std::endl;

  return 0;
}

 

 

C++11 부터 typedef 대신에 좀 더 직관적인 using 이라는 키워드를 사용할 수 있다.

typedef Ratio_add<rat, rat2> rat3;
using rat3 = Ratio_add<rat, rat2>;

 

 

 

생각해보기- 문제1

N 번째 피보나치 수를 나타내는 TMP 를 만들어보세요. 참고로 피보나치 수는, N 번째 항이 N - 1 번째 항과 N - 2 번째 항의 합으로 정의되는 수 입니다. 참고로 1, 1, 2, 3, 5, ... 로 진행됩니다.(난이도 : 하)

더보기
#include <iostream>

template <int N>
struct fib {
    static const int result = fib<N-1>::result + fib<N-2>::result;
};

template <>
struct fib<1> {
    static const int result = 1;
};

template <>
struct fib<2> {
    static const int result = 1;
};

 

 

 

9-4. 템플릿 메타 프로그래밍 2 (Template Meta programming 2)

 

 

단위 라이브러리

 

Dim 구조체를 이용하여 단위 별로 계산할 수 있는 프로그램을 만들어본다.

#include <iostream>

template <int X, int Y>
struct GCD {	//최대 공약수
	static const int value = GCD<Y, X% Y>::value;
};

template <int X>
struct GCD<X, 0> {
	static const int value = X;
};

template <int N, int D =1 >
struct Ratio {
private:
	const static int _gcd = GCD<N, D>::value;

public:
	typedef Ratio<N / _gcd, D / _gcd> type;
	static const int num = N / _gcd;
	static const int den = D / _gcd;
};

template<class R1, class R2>
struct _Ratio_add {
	using type = Ratio<R1::num* R2::den + R2::num * R1::den, R1::den* R2::den>;
};

template<class R1, class R2>
struct Ratio_add: _Ratio_add<R1,R2>::type{};

template <class R1, class R2>
struct _Ratio_subtract {
	using type = Ratio<R1::num* R2::den - R2::num * R1::den, R1::den* R2::den>;
};

template <class R1, class R2>
struct Ratio_subtract : _Ratio_subtract<R1, R2>::type {};

template <class R1, class R2>
struct _Ratio_multiply {
	using type = Ratio<R1::num* R2::num, R1::den* R2::den>;
};

template <class R1, class R2>
struct Ratio_multiply : _Ratio_multiply<R1, R2>::type {};

template <class R1, class R2>
struct _Ratio_divide {
	using type = Ratio<R1::num* R2::den, R1::den* R2::num>;
};

template <class R1, class R2>
struct Ratio_divide : _Ratio_divide<R1, R2>::type {};

template <typename U, typename V, typename W>
struct Dim {
	using M = U;
	using L = V;
	using T = W;

	using type = Dim<M, L, T>;
};

template <typename U, typename V>
struct add_dim_ {
	typedef Dim<typename Ratio_add<typename U::M, typename V::M>::type,
		typename Ratio_add<typename U::L, typename V::L>::type,
		typename Ratio_add<typename U::T, typename V::T>::type>
		type;
};

template <typename U, typename V>
struct subtract_dim_ {
	typedef Dim<typename Ratio_subtract<typename U::M, typename V::M>::type,
		typename Ratio_subtract<typename U::L, typename V::L>::type,
		typename Ratio_subtract<typename U::T, typename V::T>::type>
		type;
};

template <typename T, typename D>
struct quantity {
	T q;
	using dim_type = D;

	quantity operator+ (quantity<T, D> quant) {
		return quantity<T, D>(q + quant.q);
	}

	quantity(T q): q(q){}
};

int main() {
	using one = Ratio<1, 1>;
	using zero = Ratio<0, 1>;

	quantity<double, Dim<one, zero, zero>> kg(1);
	quantity<double, Dim<zero, one, zero>> meter(1);
	quantity<double, Dim<zero, zero, one>> second(1);

	// Good
	kg + kg;

	//Bad
	//kg + meter;
}

 

이번꺼 너무 어렵다....

 

 

  •  typename
struct check_div<N, typename divide<N, two>::result> {

 

타입/ 값 중에 어떤 것에 속하는지를 명시해주어야 하는데, 위와 같이 타입 형식에는 typename을 적어준다.

check_div는 값이라서 적어줄 필요 없음

 

  • */ operator
template <typename D2>
quantity<T, typename add_dim_<D, D2>::type> operator*(quantity<T, D2> quant) {
  return quantity<T, typename add_dim_<D, D2>::type>(q * quant.q);
}

template <typename D2>
quantity<T, typename subtract_dim_<D, D2>::type> operator/(
  quantity<T, D2> quant) {
  return quantity<T, typename subtract_dim_<D, D2>::type>(q / quant.q);
}

quantity<T, D> operator*(T scalar) { return quantity<T, D>(q * scalar); }
quantity<T, D> operator/(T scalar) { return quantity<T, D>(q / scalar); }

 

 

 

Auto

 

컴파일러가 타입을 정확히 알아낼 수 있는 경우 굳이 그 길고 긴 타입을 적지 않고 간단히 auto 로 표현할 수 있다. (컴파일 시에 컴파일러가 추론)

auto c = sum(1, 2);  // 함수 리턴 타입으로 부터 int 라고 추측 가능
auto num = 1.0 + 2.0;  // double 로 추측 가능!
auto some3(10);	// SomeClass 객체를 만들까요? ---> NO, int 정수형으로 만든다
auto F = kg * meter / (second * second);	// F 의 타입 추론 가능 (리턴 타입)

 

 

- 전체 코드

#include <iostream>
#include <typeinfo>

template <int X, int Y>
struct GCD {
  static const int value = GCD<Y, X % Y>::value;
};

template <int X>
struct GCD<X, 0> {
  static const int value = X;
};

template <int N, int D = 1>
struct Ratio {
 private:
  const static int _gcd = GCD<N, D>::value;

 public:
  typedef Ratio<N / _gcd, D / _gcd> type;
  static const int num = N / _gcd;
  static const int den = D / _gcd;
};
template <class R1, class R2>
struct _Ratio_add {
  using type = Ratio<R1::num * R2::den + R2::num * R1::den, R1::den * R2::den>;
};

template <class R1, class R2>
struct Ratio_add : _Ratio_add<R1, R2>::type {};

template <class R1, class R2>
struct _Ratio_subtract {
  using type = Ratio<R1::num * R2::den - R2::num * R1::den, R1::den * R2::den>;
};

template <class R1, class R2>
struct Ratio_subtract : _Ratio_subtract<R1, R2>::type {};

template <class R1, class R2>
struct _Ratio_multiply {
  using type = Ratio<R1::num * R2::num, R1::den * R2::den>;
};

template <class R1, class R2>
struct Ratio_multiply : _Ratio_multiply<R1, R2>::type {};

template <class R1, class R2>
struct _Ratio_divide {
  using type = Ratio<R1::num * R2::den, R1::den * R2::num>;
};

template <class R1, class R2>
struct Ratio_divide : _Ratio_divide<R1, R2>::type {};

template <typename U, typename V, typename W>
struct Dim {
  using M = U;
  using L = V;
  using T = W;

  using type = Dim<M, L, T>;
};

template <typename U, typename V>
struct add_dim_ {
  typedef Dim<typename Ratio_add<typename U::M, typename V::M>::type,
              typename Ratio_add<typename U::L, typename V::L>::type,
              typename Ratio_add<typename U::T, typename V::T>::type>
      type;
};

template <typename U, typename V>
struct subtract_dim_ {
  typedef Dim<typename Ratio_subtract<typename U::M, typename V::M>::type,
              typename Ratio_subtract<typename U::L, typename V::L>::type,
              typename Ratio_subtract<typename U::T, typename V::T>::type>
      type;
};

template <typename T, typename D>
struct quantity {
  T q;
  using dim_type = D;

  quantity operator+(quantity<T, D> quant) {
    return quantity<T, D>(q + quant.q);
  }

  quantity operator-(quantity<T, D> quant) {
    return quantity<T, D>(q - quant.q);
  }

  template <typename D2>
  quantity<T, typename add_dim_<D, D2>::type> operator*(quantity<T, D2> quant) {
    return quantity<T, typename add_dim_<D, D2>::type>(q * quant.q);
  }

  template <typename D2>
  quantity<T, typename subtract_dim_<D, D2>::type> operator/(
      quantity<T, D2> quant) {
    return quantity<T, typename subtract_dim_<D, D2>::type>(q / quant.q);
  }

  // Scalar multiplication and division
  quantity<T, D> operator*(T scalar) { return quantity<T, D>(q * scalar); }

  quantity<T, D> operator/(T scalar) { return quantity<T, D>(q / scalar); }

  quantity(T q) : q(q) {}
};

template <typename T, typename D>
std::ostream& operator<<(std::ostream& out, const quantity<T, D>& q) {
  out << q.q << "kg^" << D::M::num / D::M::den << "m^" << D::L::num / D::L::den
      << "s^" << D::T::num / D::T::den;

  return out;
}

int main() {
  using one = Ratio<1, 1>;
  using zero = Ratio<0, 1>;

  quantity<double, Dim<one, zero, zero>> kg(2);
  quantity<double, Dim<zero, one, zero>> meter(3);
  quantity<double, Dim<zero, zero, one>> second(1);

  // F 의 타입은 굳이 알필요 없다!
  auto F = kg * meter / (second * second);
  std::cout << "2 kg 물체를 3m/s^2 의 가속도로 밀기 위한 힘의 크기는? " << F
            << std::endl;
}