카테고리 보관물: C++

[C++] time_point to string

system_clock의 time_point를 string으로 변경하기 위한 코드 예제

#include <iostream>
#include <iomanip>
#include <chrono>
#include <sstream>

using namespace std;
using namespace std::chrono;

int main() {
	auto tp = system_clock::now();
	auto tt = system_clock::to_time_t(tp);
	wstringstream stm;
	stm << std::put_time(localtime(&tt), L"%Y-%m-%d %H:%M:%S");
	
	wcout << stm.str() << endl;
	return 0;
}

 

template 으로 container 전달받고 enable_if로 조건에 따라 특수화하기

오늘 코드를 작성하던 중 Serializer 관련 코드에서 vector, set, map 등의 컨테이너를 위한 read, write template 함수들이 선언되어 있는 것을 보게 되었습니다.

하지만 내부적인 구조는 value만 다루는 vector, deque, list, set 과 같은 컨테이너와 key, value를 함께 다루는 map, multimap, hash_map, unordered_map 같은 컨테이너 두 분류로 나눠져있고 중복된 코드가 보였습니다.

게다가 stdext::hash_map 만 특수화 되어 있어서 std::hash_map 등 특수화 되지 않은 컨테이너를 사용하려면 또 코드를 복사해서 특수화 하는 것을 반복해야 했습니다.

이거 왠지 template< template<class,class> class T> 표현으로 굳이 특수화를 매번 추가하지 않고도 다양한 컨테이너의 Serialize를 할 수 있겠다 싶어서 관련 코드를 대략 아래 코드와 동일한 접근 법으로 수정을 했습니다.

#include <iostream>
#include <vector>
#include <deque>

using namespace std;

// template 인자 두개를 받는 Container T를 받는 함수입니다.
template< template<class,class> class T, class V, class A>
void push_back( T<V,A>& con, V&& v) {
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
}

// vector, deque 모두 push_back 함수를 사용할 수 있습니다.
int main() {
	vector<int> v;
	deque<int> d;

	push_back(v, 1);
	push_back(d, 1);
	return 0;
}

여기서 새롭게 만난 문제는 Deserialize의 경우 vector는 size를 미리 알고 있기 때문에 reserve 함수를 호출해 주고 있었습니다. 그러면 reserve 함수를 가진 컨테이너와 아닌 컨테이너를 구분하여 특수화를 해줘야 하는데 아래의 코드와 같이 enable_if를 사용하고 member 함수의 존재를 확인해 주는 template class를 구현해 주면 가능합니다.

#include <iostream>
#include <vector>
#include <deque>
#include <type_traits>

using namespace std;

// 객체가 멤버함수 reserve를 가지고 있는지 확인합니다.
template<class T>
class has_member_reserve {
	using no  = char[1];
	using yes = char[2];
	
// SFINAE 규칙에 의해서 reserve 함수가 없으면 no를 반환합니다.
	template<class C>
	static yes&  test( decltype(&C::reserve));
	template<class C>
	static no&   test( ... );
public:
	static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};

// enable_if로 reserve 함수의 존재에 따라 적절한 push_back 함수를 호출합니다.
// 여기에서도 SFINAE 원칙이 적용됩니다.
template< template<class,class> class T, class V, class A>
void push_back( T<V,A>& con, V&& v, typename enable_if<has_member_reserve<T<V,A>>::value>::type* = nullptr) {
	con.reserve(5);
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
	cout << "this container has reserve member function" << endl;
}

template< template<class,class> class T, class V, class A>
void push_back( T<V,A>& con, V&& v, typename enable_if<has_member_reserve<T<V,A>>::value==false>::type* = nullptr) {
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
	con.push_back(v);
	cout << "this container does not have reserve member function" << endl;
}

int main() {
	vector<int> v;
	deque<int> d;
	
	push_back(v, 1);
	push_back(d, 1);
	return 0;
}

이제 사용자는 Container에 대한 특별한 제한을 느낄 수 없고 또 알아서 reserve까지 호출해 주는 좋은 코드를 사용할 수 있게 되었습니다.

멀쩡한 std::string에서 std::length_error 예외가 발생하면?

오늘 멀쩡하게 돌아가던 코드에서 std::length_error 예외가 발생해서 원인을 알아보았습니다.

std::length_error는 vector, string에서 발생하는 예외이고 string의 경우 std::string::max_size() 보다 큰 문자열을 할당하려고 했을 때 발생한다고 합니다.

디버그를 해보니 멀쩡한 string객체를 string::operator = 에 넘기는데 파라메터로 넘기기 전의 string객체를 보면 정상이고 파라메터로 넘어온 string 객체는 쓰레기 값으로 채워져 있었습니다.

해결방법은 프로젝트를 Cleanup 하고 다시 빌드하면 됩니다.

string 이 포함된 obj가 어떠한 이유로 빌드가 제대로 되지 않을 때 발생하는 문제였습니다. 일부 obj는 최신이고 일부 obj는 예전 버전으로 빌드된 상태에서 link가 이루어지고 실행이 되면 이런 이상한 상태가 되어 버립니다. 이런 문제로 시간을 버리다니... ㅠㅠ

type_index 예제

std::type_index를 사용하면 타입을 id로 사용한 container를 사용할 수 있습니다.

#include <iostream>
#include <unordered_map>
#include <type_traits>
#include <typeindex>
#include <functional>

using namespace std;

unordered_map<type_index, function<void()>> mapType;

template<typename T>
void WhatIsThis(T) {
	mapType[typeid(T)]();
}
int main() {
	mapType.emplace(typeid(int), []{cout << "this is int" << endl;});
	mapType.emplace(typeid(float), []{cout << "this is float" << endl;});
	mapType.emplace(typeid(double), []{cout << "this is double" << endl;});
	mapType.emplace(typeid(const char*), []{cout << "this is const char*" << endl;});
	
	WhatIsThis(1);
	WhatIsThis(0.5f);
	WhatIsThis(0.5);
	WhatIsThis("Hello world");
	return 0;
}

 

is_function 에서 함수를 구분하기

C++에서 std::is_function 함수를 다음과 같이 사용해 보면 예상된 결과를 반환해 주지 않습니다.

#include <iostream>
#include <type_traits>

using namespace std;

class A {
	public:
	static void static_member_function() {
		cout << "hello world!!" << endl;
	}
};

int main() {
	cout << (is_function<decltype(&A::static_member_function)>::value ? "true" : "false") << endl;
	return 0;
}

위 코드의 결과는 "false" 입니다. 함수와 함수 포인터의 차이로 인해서 생기는 이슈 입니다.

is_pointer는 true를 반환하지만 is_function은 false를 반환합니다. 따라서 필요하다면 is_function_pointer 같은 템플릿 함수를 만들어 줄 필요가 있습니다. 물론 예제코드에서 &A::static_member_function 에서 &를 뺀 A::static_member_function을 넘길 경우에는 is_function이 true를 반환합니다. 문제는 함수 포인터를 넘기는 많은 경우 함수 포인터를 받아서 다른 위치에서 함수 포인터를 이용한 호출을 하기 위함입니다.

제가 만난 케이스는 Lua에 값을 바인딩 할 때 함수일 경우와 값일 경우를 구분하기 위해서 사용하는 과정에서 만난 이슈입니다.

template<typename T>
void SetGlobal(const char* name, T arg) {
    std::_If<std::is_function<T>::value,
        FunctionBinder<T>,  // lua에 함수를 바인딩 하는 functor
        ValueBinder<T>      // lua에 변수를 바인딩 하는 functor
    >::type() (L, name, arg);
}

위의 경우 T로 함수 포인터가 넘어오게 되는데 만들고 보니 is_function에서 false가 넘어오길래 다음과 같이 수정하여 해결하였습니다.

template<typename T>
void SetGlobal(const char* name, T arg) {
    std::_If<std::is_pinter<T>::value&&std::is_function<std::remove_pointer<T>::type>::value,
        FunctionBinder<T>,  // lua에 함수를 바인딩 하는 functor
        ValueBinder<T>      // lua에 변수를 바인딩 하는 functor
    >::type() (L, name, arg);
}