정보

C++ 컴파일 타임에 컴파일러 확인

1. Visual C++ 컴파일러

Visual C++ 컴파일러는 _MSC_VER 전처리기가 등록되었는지 확인하면 되며, 컴파일러 버전은 _MSC_VER 전처리기의 값을 체크하면 된다. 이 문서 참고.

2. GNU Compiler Collection 컴파일러

GCC 컴파일러는 __GNUC__ 전처리기가 등록되었는지 확인하면 되며, 컴파일러 버전은 __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ 전처리기의 각 값을 체크하면 된다. 이 문서 참고.

3. LLVM Clang 컴파일러

Clang 컴파일러는 __clang__ 전처리기가 등록되었는지 확인하면 되며, 컴파일러 버전은 __clang_major__, __clang_minor__, __clang_patchlevel__ 전처리기의 각 값을 체크하면 되며 한꺼번에 체크하고 싶다면 __clang_version__을 체크하면 된다. 이 문서 참고.

정보

C++ 컴파일 타임에 CPU 아키텍처 확인

1. IA32

IA32 아키텍처(Intel 32-bit Architecture)는 C++ 컴파일러에서 _M_IX86, __i386__ 등의 전처리기 중 하나 이상을 자동으로 등록하며, 컴파일러에 따라 많이 다르지만 보통 Visual C++ 컴파일러와 GCC/Clang 컴파일러는 각각 _M_IX86, __i386__을 많이 사용하는 듯 하다. 이 외의 컴파일러에 대해서는 5번 항목 참조.

2. AMD64

AMD64(x86-64, x64, …)는 C++ 컴파일러에서 _M_AMD64, __amd64__ 등의 전처리기 중 하나 이상을 자동으로 등록하며, 컴파일러에 따라 다르지만 보통 Visual C++ 컴파일러와 GCC/Clang 컴파일러는 각각 _M_AMD64, __amd64__을 많이 사용하는 듯 하다. 이 외의 컴파일러에 대해서는 5번 항목 참조.

3. ARM

ARM은 C++ 컴파일러에서 _M_ARM, __arm__ 등의 전처리기 중 하나 이상을 자동으로 등록한다. 이 외의 컴파일러에 대해서는 5번 항목 참조.

4. ARM64

ARM64는 C++ 컴파일러에서 __aarch64__ 전처리기를 자동으로 등록하며, Visual C++ 컴파일러에서는 아직 지원하지 않기 때문에 전처리기를 등록하지 않는다. 이 외의 컴파일러에 대해서는 5번 항목 참조.

5. 그 외

그 외의 아키텍처에 대해서는 이 문서를 참조하면 된다. Alpha, HP/PA RISC, IA64, Motorola 68k, MIPS, PowerPC 등 다양한 아키텍처에 대한 전처리기를 안내하고 있다.

정보

C++ 컴파일 타임에 플랫폼 확인

1. Windows 계열

Windows 계열은 C++ 컴파일러에서 _WINDOWS나 _WIN32, _WIN64, WIN32, WIN64 중 하나의 전처리기를 자동으로 등록한다. 이 중 하나라도 적용된 것이 있다면 Windows 계열용으로 컴파일한다는 말.

세부적인 플랫폼 체크는 Windows.h 헤더파일을 포함한 후 가능하다.

1.1. Windows Desktop

Windows Desktop는 WINAPI_FAMILY_PARTITION 매크로를 이용해 WINAPI_PARTITION_DESKTOP이 검출됐거나 WINAPI_FAMILY_DESKTOP_APP의 정의가 되지 않았다면 해당된다.

1.2. Windows RT(Windows 8(.1) Store App, Universal Windows App)

Windows App은 Windows Desktop으로 검출이 되지는 않았으나 WINAPI_FAMILY_PARTITION 매크로를 이용해 WINAPI_FAMILY_PC_APP이 검출됐다면 해당된다.

1.3. Windows Phone(Windows Phone 8(.1) App, Universal Windows App Mobile)

Windows Phone은 Windows Desktop으로 검출이 되지는 않았으나 WINAPI_FAMILY_PARTITION 매크로를 이용해 WINAPI_FAMILY_PHONE_APP이 검출됐다면 해당된다.

2. EMScripten

EMScripten은 C++ 컴파일러에서 EMSCRIPTEN 전처리기를 자동으로 등록한다.

3. Apple OS

Apple의 모든 OS는 C/Objective-C/Objective-C++ 컴파일러에서 __APPLE__ 전처리기를 자동으로 등록한다.

세부적인 플랫폼 체크는 TargetConditionals.h 헤더파일을 포함한 후 가능하다.

3.1. macOS(Mac OS X, OS X)

macOS는 TARGET_OS_MAC이 1이고 TARGET_OS_IOS나 TARGET_OS_SIMULATOR 등 다른 요소가 0이면 해당된다.

3.2. iOS(iPhoneOS)

iOS는 TARGET_OS_IOS나 TARGET_OS_SIMULATOR가 1이면 해당된다.

3.3. watchOS

watchOS는 TARGET_OS_WATCH가 1이면 해당된다.

3.4. tvOS

tvOS는 TARGET_OS_TV가 1이면 해당된다.

4. Android

Android는 C/C++ 컴파일러에서 __ANDROID__ 전처리기를 자동으로 등록한다.

5. UNIX(Linux)

UNIX 계열 OS는 C/C++ 컴파일러에서 __unix__, __linux__ 등의 전처리기를 하나 이상 자동으로 등록하며, 이 중 Android는 __ANDROID__ 전처리기가 같이 포함되기 때문에 __ANDROID__가 등록되지 않은 경우 PC용 UNIX 계열 OS라고 볼 수 있다.

6. 최종적으로 내가 원하는 코드

#if ( defined ( _WINDOWS ) || defined ( _WIN32 ) || defined ( _WIN64 ) || defined ( WIN32 ) || defined ( WIN64 ) )
# include <Windows.h>
# if WINAPI_FAMILY_PARTITION ( WINAPI_PARTITION_DESKTOP ) || !defined ( WINAPI_FAMILY_DESKTOP_APP )
# define NBPlatformWindowsNT 1
# define NBPlatformWindowsRT 0
# elif WINAPI_FAMILY_PARTITION ( WINAPI_FAMILY_PC_APP )
# define NBPlatformWindowsNT 0
# define NBPlatformWindowsRT 1
# include <wrl.h>
# include <wrl/client.h>
# endif
#else
# define NBPlatformWindowsNT 0
# define NBPlatformWindowsRT 0
#endif
#if defined ( EMSCRIPTEN )
# include <emscripten/emscripten.h>
# include <emscripten/html5.h>
# define NBPlatformWeb 1
#else
# define NBPlatformWeb 0
#endif
#if defined ( __APPLE__ )
# include <TargetConditionals.h>
# define NBPlatformOSX TARGET_OS_MAC && !( TARGET_OS_IOS || TARGET_OS_SIMULATOR )
# define NBPlatformiOS TARGET_OS_IOS || TARGET_OS_SIMULATOR
# import <Foundation/Foundation.h>
# if ( NBPlatformOSX )
# import <Cocoa/Cocoa.h>
# else
# import <UIKit/UIKit.h>
# endif
#else
# define NBPlatformOSX 0
# define NBPlatformiOS 0
#endif
#if defined ( __ANDROID__ )
# include <jni.h>
# include <android/api-level.h>
# include <android/native_activity.h>
# include <android/native_window.h>
# include <android/input.h>
# include <android/window.h>
# include <android/configuration.h>
# include <android/asset_manager.h>
# include <android/obb.h>
# include <android/looper.h>
# include <android/keycodes.h>
# include <android/sensor.h>
# include <android/storage_manager.h>
# include <android/log.h>
# define NBPlatformAndroid 1
#else
# define NBPlatformAndroid 0
#endif
#if ( defined ( __unix__ ) || defined ( __linux__ ) ) && !defined ( __ANDROID__ )
# define NBPlatformUNIX 1
#else
# define NBPlatformUNIX 0
#endif
미분류

C++에서 Lambda Expression의 Capture를 받을 수 있는 함수포인터 사용하기

C++의 기존 방식의 함수포인터로는 람다 표현식의 Capture를 받을 수 없다. Capture란 람다 표현식을 정의한 함수의 변수 등을 사용할 수 있게 해주는 장치이다.

아래와 같은 식은 C++ 컴파일러에서 오류를 일으킨다.

typedef void ( *EXAMPLE ) ();
int main ( void )
{
	int a = 10, b = 20;
	EXAMPLE example = [ & ] () { printf ( "a = %d, b = %d\n", a, b ); };
	example ();

	return 0;
}

이 코드를 컴파일하면 아래와 같은 오류가 발생한다.

C2440 ‘초기화 중’: ‘main::’에서 ‘EXAMPLE'(으)로 변환할 수 없습니다.

이 오류를 해결할 수 있는 방법은 두 가지가 있다. 다만 한 방법은 함수의 매개변수로 넘길 수 있는 함수포인터 데이터 타입을 구성할 수 없다.

1. auto 타입 사용하기

C++ 11에서 추가된 auto 자료형을 사용하면 람다 표현식에 Capture를 넘길 수 있다.

int main ( void )
{
	int a = 10, b = 20;
	auto example = [ & ] () { printf ( "a = %d, b = %d\n", a, b ); };
	example ();

	return 0;
}

이 방법을 사용하면 사용할 함수의 함수포인터 데이터 타입을 정의할 수 없어서 함수의 매개변수에서는 람다 표현식을 받을 수 없다는 단점이 있다.

2. std::function 템플릿 사용하기

마찬가지로 C++ 11에서 추가된 STL인 std::function 템플릿 클래스를 사용하면 람다 표현식에 Capture를 넘길 수 있다. std::function 템플릿 클래스는 functional 헤더파일에 있다.

#include <functional>
typedef std::function EXAMPLE;
int main ( void )
{
	int a = 10, b = 20;
	EXAMPLE example = [ & ] () { printf ( "a = %d, b = %d\n", a, b ); };
	example ();

	return 0;
}