맨위로가기

SFINAE

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

SFINAE (Substitution Failure Is Not An Error)는 C++ 템플릿 메커니즘의 특징으로, 템플릿 인수를 대체하는 과정에서 오류가 발생해도 컴파일 오류로 이어지지 않고 다른 오버로드를 선택하거나 컴파일을 계속 진행하도록 하는 기술이다. 이는 헤더 파일 포함 등으로 인해 템플릿 선언이 중복될 때 발생하는 문제를 방지하기 위해 도입되었지만, 컴파일 시간에 템플릿 인수의 속성을 검사하는 데 널리 활용된다. SFINAE는 함수 오버로딩과 결합하여 특정 타입에 특정 멤버가 존재하는지 확인하는 등의 용도로 사용되며, C++11 이후 `void_t`와 같은 기능을 통해 더욱 간결하게 활용할 수 있다. Boost 라이브러리에서도 `boost::enable_if`를 통해 SFINAE를 활용한다.

더 읽어볼만한 페이지

  • C++ - 헤더 파일
    헤더 파일은 프로그래밍 언어에서 코드 재사용성, 모듈화, 컴파일 시간 단축에 기여하며 함수 프로토타입, 변수 선언 등을 포함하고 `#include` 지시어로 소스 코드에 포함되어 사용되는 파일이다.
  • C++ - 소멸자 (컴퓨터 프로그래밍)
    소멸자는 객체가 메모리에서 제거되기 직전에 호출되는 멤버 함수로, 객체 자원 해제 및 정리 작업을 수행하며, C++ 등 여러 언어에서 구현되고 메모리 누수 방지에 기여한다.
  • 소프트웨어 디자인 패턴 - 모델-뷰-컨트롤러
    모델-뷰-컨트롤러(MVC)는 소프트웨어 디자인 패턴으로, 응용 프로그램을 모델, 뷰, 컨트롤러 세 가지 요소로 분리하여 개발하며, 사용자 인터페이스 개발에서 데이터, 표현 방식, 사용자 입력 처리를 분리해 유지보수성과 확장성을 높이는 데 기여한다.
  • 소프트웨어 디자인 패턴 - 스케줄링 (컴퓨팅)
    스케줄링은 운영 체제가 시스템의 목적과 환경에 맞춰 작업을 관리하는 기법으로, 장기, 중기, 단기 스케줄러를 통해 프로세스를 선택하며, CPU 사용률, 처리량 등을 기준으로 평가하고, FCFS, SJF, RR 등의 알고리즘을 사용한다.
SFINAE
'SFINAE (Substitution failure is not an error)'
정의템플릿 인스턴스화 과정에서 유효하지 않은 대체를 시도할 경우, 컴파일 오류를 발생시키는 대신 해당 후보를 제거하는 C++의 특징.
설명함수 템플릿 오버로딩 해결에 사용되며, 특정 템플릿이 주어진 인수 집합에 대해 유효한지 여부에 따라 다르게 동작하도록 함.
목적컴파일 시간에 템플릿 코드를 조건부로 활성화하거나 비활성화하는 메커니즘을 제공하여 더 유연하고 일반적인 코드를 작성할 수 있도록 함.
예시특정 멤버 함수나 형식이 존재하지 않는 클래스에 대해 템플릿을 특수화하는 경우, SFINAE를 사용하여 해당 특수화를 제거하고 다른 오버로드를 선택할 수 있음.
사용 예제
멤버 존재 확인클래스에 특정 멤버 함수가 있는지 여부에 따라 다른 코드를 실행.
형식 특성특정 형식의 특성(예: 정수형, 클래스형)에 따라 다른 템플릿을 선택.
오버로드 해결함수 오버로드 집합에서 가장 적합한 함수를 선택하는 데 사용.
기술적 세부 사항
대체 컨텍스트템플릿 매개변수가 대체되는 동안 발생하는 오류는 SFINAE를 트리거함.
오류 유형유효하지 않은 형식, 존재하지 않는 멤버, 잘못된 표현식 등이 SFINAE를 유발할 수 있음.
예외함수의 본문이나 함수의 인스턴스화 이후에 발생하는 오류는 SFINAE의 영향을 받지 않으며, 일반적인 컴파일 오류로 처리됨.
관련 기술
템플릿 메타프로그래밍SFINAE는 컴파일 시간에 복잡한 논리를 수행하는 데 사용되는 템플릿 메타프로그래밍의 핵심 기술 중 하나임.
`std::enable_if`SFINAE를 사용하여 템플릿을 조건부로 활성화하거나 비활성화하는 데 사용되는 표준 라이브러리 템플릿.
`decltype`표현식의 형식을 추론하는 데 사용되며, SFINAE와 함께 사용하여 형식에 따라 다른 코드를 실행할 수 있음.

2. 예시

SFINAE 원칙이 실제 C++ 코드에서 어떻게 적용되고 활용되는지를 보여주는 몇 가지 대표적인 예시가 있다. SFINAE는 주로 템플릿 오버로딩 해결 과정이나 템플릿 메타프로그래밍 기법에서 중요한 역할을 수행한다.

가장 기본적인 활용 사례는 함수 템플릿 오버로딩 과정에서 특정 타입 조건을 만족하지 못하는 템플릿 후보를 컴파일 오류 없이 제외하는 것이다. 예를 들어, 템플릿 인수로 전달된 타입이 특정 멤버 타입이나 함수를 가지고 있지 않을 경우, 해당 멤버에 접근하려는 템플릿 함수는 SFINAE 규칙에 따라 조용히 오버로딩 후보에서 제외된다. 이를 통해 개발자는 다양한 타입에 대해 더 유연하고 안전한 템플릿 코드를 작성할 수 있다.

또한 SFINAE는 컴파일 시점에 타입의 특정 속성을 검사하는 템플릿 메타프로그래밍 기법의 핵심 원리로 사용된다. 예를 들어, 주어진 타입이 특정 `typedef`를 가지고 있는지, 특정 멤버 함수를 포함하는지 등을 컴파일 시간에 확인하여 조건부로 코드를 다르게 생성하거나 특정 기능의 사용 가능 여부를 판단하는 데 활용될 수 있다.

아래 하위 섹션에서는 이러한 SFINAE의 활용 예시를 구체적인 코드와 함께 더 자세히 다룬다.


  • 기본 예제: SFINAE의 핵심 동작 원리를 보여주는 간단한 코드 예시를 통해 기본적인 개념을 설명한다.
  • 타입 검사 예제: SFINAE를 이용하여 타입의 특정 속성(예: `typedef`의 존재 여부)을 컴파일 시간에 확인하는 기법과 그 발전 과정을 보여준다.

2. 1. 기본 예제

SFINAE는 "Substitution Failure Is Not An Error"의 약자로, C++ 템플릿 오버로딩 해결 과정에서 발생하는 특정 종류의 오류가 컴파일 오류로 이어지지 않는 원칙을 의미한다. 즉, 템플릿 인수를 특정 위치에 치환(substitution)하여 인스턴스화할 때 실패(failure)하더라도, 이것이 바로 컴파일 오류(error)가 되지는 않고(not an error), 해당 템플릿 후보를 오버로딩 후보 집합에서 조용히 제외하는 것을 말한다.[5]

구체적으로 SFINAE는 다음과 같은 상황에서 적용된다.

# 함수 오버로딩 해결 과정에서,

# 템플릿 인수를 해당 매개변수 타입에 치환하여 만들어진 함수 시그니처가 후보 집합에 포함된다.

# 이때, 치환 과정에서 유효하지 않은 코드(예: 존재하지 않는 멤버 접근, 잘못된 타입 연산 등)가 발생하더라도,[6] C++ 표준에서 SFINAE가 적용되도록 규정한 경우에 한해,

# 해당 함수 템플릿은 컴파일 오류를 발생시키지 않고 단순히 후보 집합에서 제외된다.

만약 다른 유효한 후보 함수가 존재하여 오버로딩 해결이 성공하면, 프로그램은 정상적으로 컴파일되고 실행될 수 있다.

다음은 SFINAE의 기본적인 동작 방식을 보여주는 예제이다.



struct Test {

typedef int foo;

};

template

void f(typename T::foo) {} // 정의 #1: T 안에 foo 타입이 있어야 유효함

template

void f(T) {} // 정의 #2: 모든 타입 T에 대해 유효함

int main() {

f(10); // Test에는 foo 타입이 정의되어 있으므로 정의 #1이 호출됨

f(10); // int에는 foo 타입이 없으므로 정의 #1은 SFINAE에 의해 제외되고, 정의 #2가 호출됨

return 0;

}



위 예제에서 `f(10)` 호출은 `Test` 구조체 안에 `foo`라는 `typedef`가 존재하므로, `typename T::foo` (여기서 `T`는 `Test`)가 유효한 타입 `int`로 치환되어 첫 번째 템플릿 함수(`정의 #1`)가 성공적으로 인스턴스화되고 호출된다.

반면, `f(10)` 호출 시에는 `T`가 `int` 타입이다. `int` 타입 안에는 `foo`라는 멤버 타입이 존재하지 않으므로, `typename T::foo` (즉, `typename int::foo`)를 치환하는 과정에서 실패가 발생한다. 하지만 SFINAE 원칙에 따라 이것은 컴파일 오류로 처리되지 않고, 첫 번째 템플릿 함수(`정의 #1`)는 오버로딩 후보에서 조용히 제외된다. 그 결과, 두 번째 템플릿 함수(`정의 #2`)가 유일한 유효 후보로 남아 선택되고 호출된다.

이처럼 SFINAE는 처음에는 관련 없는 템플릿 선언(예: 헤더 파일을 통해 포함된 선언) 때문에 의도치 않게 프로그램이 잘못된 형식으로 컴파일되는 것을 방지하기 위해 도입되었다. 하지만 개발자들은 이 동작을 이용하여 컴파일 시점에 타입의 속성을 검사하는 템플릿 메타프로그래밍 기법으로 유용하게 활용하고 있다.

2. 2. 타입 검사 예제

SFINAE는 원래 관련 없는 템플릿 선언이 존재할 때 프로그램 오류를 방지하기 위해 도입되었지만, 컴파일 시간에 타입의 속성을 확인하는 인트로스펙션 기법으로 유용하게 활용될 수 있음이 발견되었다. 특히 템플릿 인스턴스화 시점에 해당 템플릿 인수가 특정 조건을 만족하는지 검사하는 데 사용된다.

예를 들어, SFINAE를 이용하면 특정 타입 내부에 원하는 typedef가 정의되어 있는지 컴파일 시간에 확인할 수 있다. 다음은 C++98/03 스타일의 구현 예시이다.

#include

template

struct has_typedef_foobar {

// 크기가 다른 두 타입 'yes'와 'no'를 정의한다.

// sizeof(yes)는 1, sizeof(no)는 2가 된다.

typedef char yes[1];

typedef char no[2];

// T 타입 내부에 'foobar'라는 typedef가 있으면 이 함수 템플릿이 선택된다.

template

static yes& test(typename C::foobar*); // 포인터 타입으로 받는 이유는 널 포인터(nullptr)를 인자로 넘기기 위함이다.

// 'foobar' typedef가 없거나 다른 이유로 위 함수 템플릿 선택에 실패하면,

// 이 함수 템플릿이 대신 선택된다. '...'는 모든 타입의 인자를 받을 수 있으며,

// 타입 변환 우선순위가 가장 낮아 첫 번째 함수가 가능하면 우선 선택된다.

template

static no& test(...);

// test(nullptr) 호출 결과의 크기를 비교하여 'foobar'의 존재 여부를 확인한다.

// sizeof(test(nullptr)) == sizeof(yes)이면 'foobar'가 존재한다고 판단한다.

static const bool value = sizeof(test(nullptr)) == sizeof(yes);

};

struct foo {

typedef float foobar; // 'foobar' typedef를 가짐

};

int main() {

std::cout << std::boolalpha; // bool 값을 true/false로 출력

std::cout << has_typedef_foobar::value << std::endl; // false 출력 (int에는 foobar typedef 없음)

std::cout << has_typedef_foobar::value << std::endl; // true 출력 (foo에는 foobar typedef 있음)

return 0;

}

만약 타입 T 안에 foobar라는 중첩 타입(typedef)이 정의되어 있다면, `typename C::foobar*` 형태의 인자를 받는 첫 번째 test 함수 템플릿이 성공적으로 인스턴스화되고, nullptr를 인자로 전달할 수 있게 된다. 이때 함수의 반환 타입은 yes& 이므로, sizeof(test(nullptr))의 결과는 sizeof(yes) 즉, 1이 된다. 반면, T 안에 foobar가 정의되어 있지 않으면 첫 번째 test 함수 템플릿 인스턴스화는 실패하고(SFINAE 규칙에 의해 오류가 아닌 후보 제외 처리됨), ... 인자를 받는 두 번째 test 함수 템플릿이 유일한 후보가 된다. 이때 반환 타입은 no& 이므로 sizeof(test(nullptr))의 결과는 sizeof(no) 즉, 2가 된다. ... 인자는 모든 타입을 받을 수 있지만 타입 변환 우선순위가 가장 낮기 때문에, 첫 번째 함수 템플릿이 유효하다면 항상 첫 번째가 선택되어 모호성을 피할 수 있다.

C++11에서는 std::true_type, std::false_type과 using 선언(타입 별칭), void_t (C++17 표준, C++11에서는 직접 정의 가능) 등을 활용하여 이 기법을 더 간결하게 구현할 수 있다.

#include

#include // std::true_type, std::false_type 사용

// C++17의 std::void_t와 유사한 기능. 어떤 타입이든 void로 만든다.

// SFINAE를 유발시키는 용도로 사용된다.

template

using void_t = void;

// 기본 템플릿: T::foobar가 없거나 다른 이유로 아래 특수화 버전이 실패하면 선택됨.

template

struct has_typedef_foobar : std::false_type {}; // 결과로 false_type 상속

// 템플릿 특수화: T::foobar가 유효한 타입일 경우 이 버전이 선택됨.

// void_t 부분에서 T::foobar 접근이 실패하면 SFINAE 발생.

template

struct has_typedef_foobar> : std::true_type {}; // 결과로 true_type 상속

struct foo {

using foobar = float; // C++11 using 선언 사용

};

int main() {

std::cout << std::boolalpha;

std::cout << has_typedef_foobar::value << std::endl; // false 출력

std::cout << has_typedef_foobar::value << std::endl; // true 출력

return 0;

}

C++17에서는 이러한 타입 특성 검사 기법이 std::is_detected 와 같은 형태로 표준 라이브러리에 포함되어 더욱 간편하게 사용할 수 있다.

#include

#include // std::is_detected 등 포함 (실험적 기능일 수 있음, 컴파일러 지원 확인 필요)

// 검사하고자 하는 표현식(T::foobar 타입)을 정의

template

using foobar_t = typename T::foobar; // C++11 using 활용

struct foo {

using foobar = float;

};

int main() {

std::cout << std::boolalpha;

// std::is_detected<검사할 표현식, 검사할 타입>::value 형태로 사용

std::cout << std::is_detected::value << std::endl; // false 출력

std::cout << std::is_detected::value << std::endl; // true 출력

return 0;

}

(참고: std::is_detected는 C++20 표준 라이브러리 유틸리티로 정식 포함될 예정이었으나, 최종적으로 포함되지 않고 Library Fundamentals TS v2 등에 남아있다. 실제 사용 시에는 컴파일러 및 표준 라이브러리 버전에 따라 지원 여부가 다를 수 있다.)

Boost 라이브러리에서는 boost::enable_if[7] 와 같은 유틸리티들이 SFINAE 원리를 기반으로 구현되어, 조건부 컴파일 및 템플릿 메타프로그래밍에 널리 사용된다.

3. C++11에서의 간소화

C++11 표준은 SFINAE(Substitution Failure Is Not An Error) 기법을 더 간결하게 사용할 수 있는 방법을 도입했다. 이전 버전의 C++에서 SFINAE를 구현하기 위해 사용되던 복잡한 기법들, 예를 들어 `sizeof` 연산자를 활용한 트릭 대신, 더 직관적인 방법을 제공한다.

주요 간소화 방법 중 하나는 `void_t`라는 별칭 템플릿(alias template)을 활용하는 것이다. `void_t`는 어떤 타입 인자들을 받더라도 항상 `void` 타입을 나타내는 간단한 템플릿이다. 이를 이용하면 특정 타입 표현식(예: 멤버 타입의 존재 여부)이 유효한지를 컴파일 시간에 검사하는 코드를 훨씬 단순하게 작성할 수 있다.

예를 들어, 타입 `T`가 `foobar`라는 중첩 타입을 가지고 있는지 확인하는 코드는 `void_t`를 사용하여 다음과 같이 간결하게 표현될 수 있다. (상세 구현 및 설명은 하위 섹션 참조)



#include // std::true_type, std::false_type 등을 사용하기 위해 필요

// 어떤 타입 인자든 void로 변환하는 별칭 템플릿

template

using void_t = void;

// 기본 템플릿: T::foobar가 유효하지 않으면 이 템플릿이 선택됨 (결과는 false)

template

struct has_typedef_foobar : std::false_type {};

// 템플릿 특수화: T::foobar가 유효한 타입 표현식이면 void_t는 void가 되어 이 특수화 버전이 선택됨 (결과는 true)

template

struct has_typedef_foobar> : std::true_type {};



이러한 `void_t`를 이용한 기법은 C++17에서 `std::is_detected`라는 이름으로 표준 라이브러리에 포함되어 더욱 발전했다. ([http://en.cppreference.com/w/cpp/experimental/lib_extensions_2 라이브러리 기본 v2 (n4562)] 제안 참고) 이는 SFINAE를 활용한 타입 검사를 더욱 표준적이고 가독성 높게 만들어 주었다. (상세 내용은 하위 섹션 참조)

한편, 부스트(Boost) 라이브러리에서는 이미 C++11 이전부터 `boost::enable_if`[3] 와 같은 도구를 통해 SFINAE를 적극적으로 활용하여 템플릿 메타프로그래밍에서 조건부 컴파일 등의 기능을 구현해왔다.

3. 1. void_t 사용

C++11에서는 템플릿 메타프로그래밍에서 타입 특성을 검사하는 SFINAE 기법을 더 간결하게 사용할 수 있도록 `void_t` 라는 별칭 템플릿(alias template)을 활용할 수 있다. `void_t`는 어떤 타입 인자들을 받더라도 항상 `void` 타입을 나타낸다.



#include

#include

// 어떤 템플릿 인수든 void가 된다.

template

using void_t = void;

// 기본 템플릿: T에 foobar 타입 정의가 없으면 이 템플릿이 선택됨

template

struct has_typedef_foobar : std::false_type {};

// 템플릿 특수화: T::foobar가 유효한 타입 표현식이면 void_t는 void가 되어 이 특수화 버전이 선택됨

template

struct has_typedef_foobar> : std::true_type {};

struct foo {

using foobar = float; // foo 구조체는 foobar 타입을 가지고 있음

};

int main() {

std::cout << std::boolalpha;

// int 타입에는 foobar 멤버 타입이 없으므로 false 출력

std::cout << has_typedef_foobar::value << std::endl;

// foo 구조체에는 foobar 멤버 타입이 있으므로 true 출력

std::cout << has_typedef_foobar::value << std::endl;

return 0;

}



위 코드에서 `has_typedef_foobar`는 타입 `T`가 `foobar`라는 중첩 타입을 가지고 있는지 컴파일 시간에 확인한다. 만약 `T::foobar`가 유효한 타입이라면, `void_t`는 `void`가 되고, 템플릿 특수화 버전인 `has_typedef_foobar`가 선택되어 `std::true_type`을 상속받는다. 만약 `T::foobar`가 유효하지 않다면 SFINAE 규칙에 따라 특수화 버전은 후보에서 제외되고, 기본 템플릿 `has_typedef_foobar`가 선택되어 `std::false_type`을 상속받는다.

C++17에서는 이 기법이 `std::is_detected`라는 이름으로 표준 라이브러리에 포함되어 더욱 간결하게 사용할 수 있게 되었다. [http://en.cppreference.com/w/cpp/experimental/lib_extensions_2 라이브러리 기본 v2 (n4562)] 제안에서 표준화된 이 감지 관용구(detection idiom)를 사용하면 위 코드를 다음과 같이 작성할 수 있다.



#include

#include // std::is_detected를 사용하기 위해 필요

// 검사하고자 하는 표현식 (T::foobar 타입)을 정의하는 별칭 템플릿

template

using has_typedef_foobar_t = typename T::foobar;

struct foo {

using foobar = float;

};

int main() {

std::cout << std::boolalpha;

// std::is_detected는 Args 타입으로 Operation 표현식이 유효한지 검사

// int 타입으로 has_typedef_foobar_t (즉, int::foobar)는 유효하지 않으므로 false 출력

std::cout << std::is_detected::value << std::endl;

// foo 타입으로 has_typedef_foobar_t (즉, foo::foobar)는 유효하므로 true 출력

std::cout << std::is_detected::value << std::endl;

return 0;

}



`std::is_detected`는 첫 번째 템플릿 인자로 주어진 '연산(Operation)' 템플릿을 나머지 인자들로 적용했을 때 유효한 표현식인지 여부를 `std::true_type` 또는 `std::false_type`으로 알려준다. 이를 통해 `void_t`를 직접 사용하는 것보다 더 직관적으로 타입 특성을 검사할 수 있다.

3. 2. is_detected 사용 (C++17)

C++17 표준에서는 타입 검사를 위한 `std::is_detected`와 같은 도구를 제공하여 SFINAE 기법을 더 직관적이고 간결하게 사용할 수 있게 되었다. 이는 Library Fundamentals TS v2에서 제안된 탐지 관용구(detection idiom)가 표준 라이브러리에 반영된 결과이다.

`is_detected`는 특정 타입 `T`에 대해 주어진 표현식(예: `T::foobar`와 같은 중첩 타입 존재 여부)이 유효한지를 컴파일 시간에 확인하는 데 사용된다.

다음은 `is_detected`를 사용하여 특정 타입 내부에 `foobar`라는 중첩 타입 정의가 있는지 확인하는 예제이다.



#include

#include // C++17 표준 라이브러리의 헤더에 포함됨 (이전에는 )

// 검사하려는 표현식을 정의하는 별칭 템플릿

template

using has_typedef_foobar_t = typename T::foobar;

struct foo {

using foobar = float; // 'foo' 구조체는 'foobar'라는 중첩 타입을 가짐

};

int main() {

std::cout << std::boolalpha;

// std::is_detected<표현식, 타입>::value 형태로 사용

// int 타입에는 foobar 중첩 타입이 없으므로 false 출력

std::cout << std::is_detected::value << std::endl;

// foo 타입에는 foobar 중첩 타입이 있으므로 true 출력

std::cout << std::is_detected::value << std::endl;

return 0;

}



이 코드는 C++11의 `void_t`를 사용하는 방식보다 훨씬 간결하며, 타입 특성을 확인하려는 의도를 명확하게 드러낸다. `std::is_detected`는 템플릿 메타프로그래밍 코드를 작성할 때 발생하는 복잡성을 줄여주고 가독성을 높이는 데 도움을 준다.

4. 응용

SFINAE는 함수 오버로딩 과정에서 발생하는 특별한 규칙을 가리키는 용어이다.[5] 템플릿 함수의 후보를 결정할 때, 특정 템플릿 인수로 인해 함수 시그니처 생성(치환)에 실패하더라도 컴파일 오류로 이어지지 않고 단순히 해당 함수를 후보에서 제외하는 것을 의미한다.

원래 SFINAE는 관련 없는 템플릿 선언이 의도치 않게 코드에 포함되었을 때 발생할 수 있는 문제를 방지하기 위해 도입되었다. 하지만 개발자들은 이 규칙을 이용하여 컴파일 시간에 코드의 속성을 확인하는 인트로스펙션 기법으로 활용할 수 있음을 발견했다. 예를 들어, 특정 타입 내부에 원하는 멤버 변수나 타입 정의(`typedef` 또는 `using`)가 존재하는지 등을 컴파일 단계에서 알아낼 수 있다.

이러한 컴파일 타임 인트로스펙션 기능은 템플릿 메타프로그래밍에서 유용하게 사용되며, 조건부 컴파일이나 타입 특성(type traits) 구현의 기반이 된다. 대표적으로 Boost 라이브러리의 `boost::enable_if`[7]와 같은 기능들이 SFINAE 원리를 이용하여 구현되었다. C++11 이후 표준 라이브러리에서도 SFINAE를 활용한 다양한 타입 특성 유틸리티들이 추가되어, 개발자들이 더 간편하게 관련 기능을 사용할 수 있게 되었다.

4. 1. 컴파일 타임 인트로스펙션

SFINAE는 원래 관련 없는 템플릿 선언이 (예를 들어 헤더 파일 포함을 통해) 보일 때 잘못된 형식의 프로그램이 생성되는 것을 피하기 위해 도입되었다.[5] 그러나 이후 개발자들은 이 동작이 컴파일 시간에 인트로스펙션을 수행하는 데 유용하다는 것을 발견했다. 특히, 템플릿을 인스턴스화할 때 템플릿 인수가 특정 속성(예: 특정 멤버 변수나 타입 정의의 존재 유무)을 가지고 있는지 감지하는 데 활용될 수 있다.

예를 들어, SFINAE를 사용하면 주어진 자료형(type)에 특정 `typedef`가 포함되어 있는지 컴파일 시간에 확인할 수 있다. 다음은 그 예시이다.



#include

template

struct has_typedef_foobar {

// 크기가 다른 두 개의 배열 타입을 정의한다.

// sizeof(yes)는 1이고, sizeof(no)는 2이다.

typedef char yes[1];

typedef char no[2];

// T에 foobar라는 typedef가 있으면 이 함수 템플릿이 선택된다.

// typename C::foobar* 부분이 SFINAE의 핵심이다. T::foobar가 유효한 타입이어야 한다.

template

static yes& test(typename C::foobar*);

// 그렇지 않으면 이 함수 템플릿이 선택된다. (가변 인자 함수)

// 가변 인자(...)는 모든 타입의 인자를 받을 수 있으며, 오버로딩 우선순위가 가장 낮다.

template

static no& test(...);

// test(nullptr)를 호출했을 때 반환되는 타입의 크기를 비교한다.

// 크기가 sizeof(yes)와 같으면 T 안에 foobar typedef가 존재한다는 의미이다.

static const bool value = sizeof(test(nullptr)) == sizeof(yes);

};

struct foo {

typedef float foobar; // foo 구조체 안에는 foobar typedef가 있다.

};

int main() {

std::cout << std::boolalpha;

// int에는 foobar typedef가 없으므로 SFINAE에 의해 첫 번째 test 함수는 제외되고,

// 두 번째 test 함수가 선택되어 반환 타입은 no&가 된다. 따라서 false 출력.

std::cout << has_typedef_foobar::value << std::endl;

// foo에는 foobar typedef가 있으므로 첫 번째 test 함수가 선택되어 반환 타입은 yes&가 된다.

// 따라서 true 출력.

std::cout << has_typedef_foobar::value << std::endl;

return 0;

}



위 코드에서 `has_typedef_foobar` 템플릿은 `T`라는 타입 내부에 `foobar`라는 이름의 `typedef`가 정의되어 있는지 확인한다.

만약 `T::foobar`가 유효한 타입이라면, `test` 함수의 첫 번째 오버로딩 버전(반환 타입 `yes&`)이 인스턴스화될 수 있다. `typename C::foobar*` 부분에서 타입 치환이 성공하기 때문이다.

만약 `T::foobar`가 유효하지 않다면(예: `T`가 `int`인 경우), 첫 번째 `test` 함수 템플릿의 인스턴스화는 실패하지만, SFINAE 규칙 덕분에 컴파일 오류가 발생하지 않고 해당 함수는 오버로딩 후보 집합에서 조용히 제외된다. 그러면 두 번째 `test` 함수(반환 타입 `no&`)가 유일한 후보로 남게 되어 선택된다.

최종적으로 `sizeof(test(nullptr))`를 계산하여 반환된 타입의 크기가 `sizeof(yes)` (즉, 1)와 같은지 비교한다. 같다면 `T` 안에 `foobar` typedef가 존재하는 것이고, 다르다면(즉, `sizeof(no)`와 같다면) 존재하지 않는 것이다.

C++11 표준에서는 타입 특성(type traits)과 `decltype`, 별칭 템플릿(alias template) 등을 이용하여 이 기법을 더 간결하게 구현할 수 있다.



#include

#include // std::true_type, std::false_type 포함

// 어떤 템플릿 인수든 void가 된다.

// 템플릿 인수가 유효한 타입 표현식을 구성하는지 확인하는 데 사용된다.

// (C++17부터 std::void_t로 표준 라이브러리에 포함됨)

template

using void_t = void;

// 기본 템플릿: T::foobar가 없거나 유효하지 않으면 이 템플릿이 선택됨.

// 기본적으로 false_type을 상속받아 value가 false가 된다.

template

struct has_typedef_foobar : std::false_type {};

// 부분 특수화: T::foobar가 유효한 타입이면 void_t가 void로 평가되어

// 이 특수화 버전이 기본 템플릿보다 우선적으로 선택됨.

// true_type을 상속받아 value가 true가 된다.

template

struct has_typedef_foobar> : std::true_type {};

struct foo {

using foobar = float; // C++11의 using 문법으로 typedef와 유사하게 사용

};

int main() {

std::cout << std::boolalpha;

std::cout << has_typedef_foobar::value << std::endl; // false 출력

std::cout << has_typedef_foobar::value << std::endl; // true 출력

return 0;

}



C++17에서는 이 기법이 표준 라이브러리에서 `std::is_detected` 와 같은 형태로 지원되어 더욱 간결하게 표현할 수 있게 되었다.



#include

#include // C++17 이후 에 관련 유틸리티 포함

// T::foobar 타입 표현이 유효한지 확인하는 헬퍼 타입 별칭

template

using foobar_t = typename T::foobar; // C++11 using 문법

struct foo {

using foobar = float;

};

int main() {

std::cout << std::boolalpha;

// std::is_detected (또는 유사한 메커니즘)를 사용하여 int::foobar 유효성 검사 -> false

std::cout << std::is_detected::value << std::endl;

// std::is_detected (또는 유사한 메커니즘)를 사용하여 foo::foobar 유효성 검사 -> true

std::cout << std::is_detected::value << std::endl;

return 0;

}



Boost 라이브러리의 `boost::enable_if`[7] 와 같은 조건부 컴파일 및 타입 특성 관련 기능들도 내부적으로 SFINAE 원리를 적극적으로 활용하여 구현되어 있다.

4. 2. Boost 라이브러리에서의 활용

부스트 라이브러리에서는 boost::enable_if[7] 등을 SFINAE를 사용하여 구현하고 있다.

참조

[1] 서적 C++ Templates: The Complete Guide Addison-Wesley Professional
[2] 간행물 ISO/IEC 14882:2003, Programming languages – C++ International Organization for Standardization
[3] 웹사이트 Boost Enable If http://www.boost.org[...]
[4] 웹사이트 任意の式によるSFINAE https://cpprefjp.git[...] 2017-02-01
[5] 서적 C++ Templates: The Complete Guide Addison-Wesley Professional
[6] 간행물 ISO/IEC 14882:2003, Programming languages — C++ International Organization for Standardization
[7] 웹사이트 Boost Enable If http://www.boost.org[...]
[8] 서적 C++ Templates: The Complete Guide https://archive.org/[...] Addison-Wesley Professional
[9] 간행물 ISO/IEC 14882:2003, Programming languages – C++ International Organization for Standardization
[10] 웹사이트 Boost Enable If http://www.boost.org[...]



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com