ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [2] - Templates - 2
    C++ 2021. 8. 8. 14:05

    SFINAE[Substitution Failure is Not an Error]

    Ref

    1. (https://en.cppreference.com/w/cpp/language/sfinae) -> Definition of the SFINAE

    2. (https://en.cppreference.com/w/cpp/language/overload_resolution) -> SFINAE applies during this Overload Resolution.

    3. (https://en.cppreference.com/w/cpp/language/adl) -> In order to compile a function call, the compiler must first perform name lookup which may involve Argument-Dependent lookup. And Argument-Dependent lookup is also known as 'König lookup' or just ADL as a abbreviation. ADL is rule for identifying invalid function names during the compile time. By the way, the overload resolution means to figure out the most appropriate definition of a overloaded function or operator. According to the IBMs.

    4. (https://www.ibm.com/docs/en/i/7.4?topic=only-overload-resolution-c)-> What is the 'overload resolution.'

    5. (https://en.cppreference.com/w/cpp/language/function_template#Template_argument_substitution) -> It is recommended that you understand 'Template Argument Substitution' before begin. But it is barely possible to fully understand what TAS does actually mean. Also, prerequisite information about what is 'Deduced type'. is below.

    6. (https://en.cppreference.com/w/cpp/language/template_argument_deduction) -> Long way to go home.

    7. (https://docs.oracle.com/cd/E19205-01/819-5267/bkage/index.html) -> This is an article from the Oracle. It explains how the compiler works when it comes to a template.

    8. (https://www.cppstories.com/2016/02/notes-on-c-sfinae/)-> This article would be the best choice to understand concept of the SFINAE principle briefly.

    9. (https://en.cppreference.com/w/cpp/language/decltype) -> Decltype

    10. (https://en.cppreference.com/w/cpp/language/value_category) -> about value category.

    11. (https://en.cppreference.com/w/cpp/language/expressions#Literals) -> What is 'Literal' in C++.

    : It's all aboutSFINAE

    대체가 실패했다는 것은 에러가 아니다. 직역하면 그런 말이 됩니다. 이 원칙은 function template의 overload resolution 동안 적용되며 이 특징은 template metaprogramming에서 사용되는 성질입니다. 명시적으로 Specified 되어있는 template의 parameter나 deduced data type을 Substituting에 실패하면 Compile error를 내는 것이 아니고 Overload Set으로부터 Specialisation을 끝내는 것입니다. 용어부터 하나도 몰라서 진짜 너무 힘들었습니다. Overload resolution, Substitution이 두 가지가 가장 큰 문제입니다. 차근차근 살펴보고 SFINAE가 뭔지 반드시 끝장내겠습니다. 일단 Function template가 무엇인지 살펴보겠습니다.

    Function Template

     Fucntion template의 명시적인 설명은 'A function template defines a family of functions.'입니다. 또 다른 설명은 Generic type에 대해서 operate할 수 있는 함수들이라는 설명도 있습니다. 즉, 서로 다른 데이터 타입에 대해서 같은 함수를 Duplicate하지 않아도 된다는 뜻을 내포합니다.

    A family of functions는 수학에서는 비슷한 형태를 가진 방정식들의 집합을 일컫는 말입니다. 해당 성질을 차용해서 비슷한 operation을 제공하는 함수들을 a family of functions라고 합니다. 그런 예시가 System Call인 exce이 있습니다. 하지만 template의 성격 상, 다른 데이터 타입에 대해서 완전히 같은 operation을 제공하게 될 것입니다. function template의 Syntax는 현재 관심사가 아니니 넘어갑니다.

    * <typename T> vs. <class T> *

    -> Its use is totally indistinct since both expressions do exactly same behaviour.

    Abbreviated function template

    auto, concept auto같은 placeholder가 함수 선언에 나타나면 마치 Template처럼 컴파일러가 다룹니다. 본문에 명시되어 있기를 C++20 표준부터 지원한다고 되어 있습니다. 즉, void F(auto)template <typename T> void F(T)와 같은 맥락으로 해석된다는 것입니다. 그래서 template <typename T>라고 쓰는 대신 '축약'하여서 단순히 concept auto를 사용하는 것입니다. 현재 사용하는 컴파일러는 이런 Syntax를 이해하지 못합니다. 상당히 최신 기술입니다. 이 축약은 Specialisation도 적용이 됩니다. 이 부분은 접한 적도 없고 현재 알만한 내용도 아니기 때문에 다음에 더 자세히 알아보도록 하겠습니다.

    현재 Compiler version에서는 C++20의 표준을 이해하지 못합니다

    Function template Instantiation

     Function template에 대해서 너무나도 중요한 사실이 하나 있습니다. Functin template자체는 함수도 아니고 어떤 타입도 아니며 그 어떤 Entity 조차 아닙니다. 그러니까 template의 definition만 있는 Source file에서는 아무런 Code가 생성되지 않습니다. 그래서 해당 소스 파일에서 무슨 코드라도 만들고 싶다면 template이 하나라도 명시적으로[Explicitly] instantiate가 되어야 합니다. 즉, template의 arguments가 결정 되어야 컴파일러가 실제로 함수를 만들 수 있습니다. 이것은 class template도 마찬가지입니다. class template만 있는 소스 파일에서는 코드가 생성되지 않습니다. function template를 instantiate하는 방법에는 몇 가지가 존재합니다.

    1. template return-type name < argument-list > ( parameter list );

    : 모든 Argument-list가 명시되어 있다면  Template Argument Deduction을 하지 않습니다. 반면에 아래에는 Argument-list가 명시되어 있지 않기 때문에 Compiler가 Template Argument deduction을 진행합니다.

    2. template teturn-type name ( parameter-list );

    3. extern template return-type name <Arugument-list > ( parameter-list );

    4. extern template return-type name ( parameter-list );

    위에서 Extern이 있고 없고 이 차이는 extern이 declaration이고 없는 것은 definition입니다. 명시적인 Definition을 수행하면 강제로 instantiation을 실시합니다.

    Overload Resolution

    "어떤 함수 호출을 컴파일 하기 위해서는 반드시 Argument-dependent lookup의 가능성이 있는 함수에 대해서 name lookup을 해야합니다." 문장 전체 구성이 문법을 제외하곤 처음 보는 단어로 이루어져 있습니다. 한국말인데 현재 '의미'를 전달받을 수가 없습니다. 하나씩 공부하고 다시 돌아와서 이 문장을 이해해보겠습니다.

    1. Name lookup

    : Name이란 '정확히' 무엇일까요? 어떤 Entity[독립체, 존재], label. 이하의 중 어느 것 혹은 그 이상의 것들을 지칭합니다. 이걸 구태여 해독(또는 뜻 자체를 풀어서 설명하지 않습니다.)하여 적지 않고 Name 혹은 이름이라고 하겠습니다. 복합적인 개념을 묶어 Name이라고 해도 충분히 이해할 수 있다는 뜻입니다.

    https://en.cppreference.com/w/cpp/language/identifiers#Names

    원문을 보면 이렇게 적혀 있습니다.

    Name lookup is the procedure by which a name, when encountered in a program, is associated with the declaration that introduced it.

    즉, 프로그램 안에서 선언에 관련된 이름과 마주쳤을 때 사용되는 일종의 절차라는 것입니다. 말이 정말 어렵습니다. 강의시간에 배운 지식들은 대체 무엇이였던 것일까요? 단 한 번도 이런 엄밀한 공부를 해본 적도 없고 필요하다는 말도 들어본 적도 없습니다. 뻘짓을 하고 있는 건 아닐까요? 이렇게 산다고 뭐가 달라지기는 하는 것일까요? 모르겠습니다. 모를 땐 예시를 들어보는 것이 최고입니다. 가장 친숙한 std::cout << std::endl;을 컴파일 하는 과정은 다음과 같은 절차를 밟습니다. 아마도 용어에 대한 문제가 또 발목을 잡겠죠. 영어문서들과 한국어 문서, 일본어 문서들을 대조하며 읽다가 느낀 재미있는 감상이 하나 있습니다. 문서의 내용이 어려우면 어려울 수록 번역 자체는 굉장히 쉬워진다는 점입니다. 영어->한국어, 일본어는 단순히 거꾸로 읽으면 해석이 정말 쉽습니다.

    1. std라는 name에 대해서 <iostream> header 안에 namespace std의 선언을 찾는 unqualified name lookup을 실시합니다. (Unqualified Name Lookup?)

    2. namespace std 안에 변수 선언을 찾는 Qualified name lookup을 Cout이라는 name에 대해서 실시합니다.

    3. namespace std 안에서 Function template declaration을 찾는 Qualified name lookup을 endl에 대해서 실시합니다.

    4. 마지막으로 operator<<에 대해서 namespace std 안에 multiple function template 선언을 찾는 argument-dependent-lookup을 실시하고 std::ostream class 안에 multiple member function 선언을 찾는 Qualified name lookup을 std::ostream::operator<< name에 대해 실시합니다.

    Unqualified name lookup이란 무엇이고 Qualified name lookup이란 무엇일까요? 이것부터 해결하겠습니다.

    * Qualified name lookup (https://en.cppreference.com/w/cpp/language/qualified_lookup)

    * Unqualified name lookup (https://en.cppreference.com/w/cpp/language/unqualified_lookup)

    : 아마 [ :: ] 이렇게 콜론이 두 번 찍혀있는 기호를 종종 보았을 텐데 이것이 The scope resolution operator입니다. 한국말로 의역하면 '스코프를 풀어 해석하는 기호' 정도일 겁니다. 이 기호 우측에 적혀 있는 name이 바로 Qualified name입니다. 종류까지 명시하면 Class의 Member들, enumeration type의 member들, namespace의 명시 등이 있습니다. 그런데 Scope resolution operator 좌측에 아무것도 없는 경우가 있습니다. 이는 namespace lookup을 global에 대해서만 실시하게 만듭니다. 예를 들어서 다음과 같이 struct의 name을 std라고 해버리면 std::cout 은 struct에 대해서 실시되지만 ::std::cout으로 사용하면 해당 scope가 아닌 global에 존재하는 Std를 찾아 수행합니다.

    namelookup은 scope resolution operator [ :: ] 의 좌측에 decltype이 오거나 아무것도 없는 경우가 아니라면 반드시 촤즉부터 시행되어야 합니다. 또, 중요한 규칙은 scope resolution을 해석할 때는 Unqualified 이든 Qualified 이는 선언된 라인 위에서부터 찾아보며 단 하나라도 선언과 일치하는 Name이 존재하면 탐색을 중단합니다. 위의 Ref 10번은 value category에 대한 내용인데 C++를 좋아한다면 꼭 읽어보는 것이 좋습니다. decltype을 이해하는데 중요한 역할을 합니다.

    반대로 Unqualified name이란 Scope resolution operator [ :: ]의 좌측에 나타나는 이름입니다. unqualified name lookup을 시행할 때는 몇 가지 경우의 수를 무시하게 됩니다. 예를 들어서 [ :: ]의 좌측에 나타난 unqualified name이 function, variable, enumeration declaration은 무시합니다. 그래서 다음과 같은 예시는 오류가 발생합니다.

    잘 생각해보면 원인을 파악할 수가 있습니다. Scope 밖에 선언된 구조체 A와 main 의 Scope안에 unsigned integer A가 있습니다. A::x는 A에 대해서 unqualified name lookup을 실행하고 위에서 Unqualified name lookup이 무시하는 몇 가지 예시 중에서 Variable이 있었습니다. 따라서 unsigned integer A는 resolution의 후보에 오르지도 못했기 때문에 static float x에 성공적으로 접근을 할 수 있었습니다. 하지만 A라고만 선언된 경우엔 Unqualified name Lookup을 실시하지만 Variable을 무시하지 않기 때문에 scope 내의 unsigned integer로 Resolution하여 오류를 발생시킨 것입니다. 이밖에도 다양한 것들이 존재하지만 여기까지만 해도 Name lookup이 무엇인지 감을 잡을 수 있습니다.

    Argument-dependent lookup

    Argument-dependent lookup(ADL)은 Overloaded operator를 비롯한 Unqualified function name을 lookup하는 작업에 관련된 몇 가지의 규칙을 일컫는 말입니다. 흔히 사용하는 std::cout << 이 ADL의 가장 대표적인 예시입니다. 참고로 std::cout은 global objects로 정의가 되어 있습니다.(https://en.cppreference.com/w/cpp/io/cout)

    이 Overloaded Operator는 일반적인 Unqualified name lookup, Scope와 namesapce뿐만 아니라 Argument에서도 namespace를 찾게 됩니다. 그래서 위의 코드에서는 Global Area에서 정의된 operator<<라는 namespace가 없지만 operator<<의 left argument인 std가 있기 때문에 ADL이 std::cout의 operator<<를 유추해옵니다. 아래의 operator<<(std::cout, "Test \n);은 위의 표현의 함수형 표현으로써 왼쪽 Argument에 명시적으로 namespace std가 적혀 있는 것을 확인해 볼 수 있습니다.

    이유는 비슷합니다 첫 번째 std::cout << endl;은 얼핏 그럴 듯 해보이지만 함수처럼 적어보면 operator<<(std::cout, endl);입니다. endl은 현재 scope에서 찾을 수 없습니다. 아래의 경우는 반대입니다. endl의 함수 호출의 형태이며 Argument인 std::cout으로부터 std namespace를 유추하여 endl함수를 수행합니다. 하지만 마지막 라인 (endl)은 애초에 함수 호출 형식이 아닙니다. 이것이 ADL에 대한 전반적인 내용입니다. Detail까지 있긴 하지만 개념만 챙기도록 합니다. 이제 Overload Resolution에 대해서 설명된 글을 읽을 수 있습니다.

    "Overload Resolution -> 함수 호출을 컴파일 하기 위해서는 ADL에 관련되어 있을 지도 모르는 함수, Template Argument Deduction에 관련되어 있을 수 있는 Function template에 대해서 Name lookup을 실시해야 한다."

    속이 시원합니다. 모든 용어를 알고 있기 때문에 Overload Resolution에 대한 설명의 첫 문장을 이해할 수 있습니다. 다음 문장이 모든 것을 정리합니다.

    "만약, name lookup을 실시하다가 몇 가지의 후보 함수들이 존재한다면 바로 이 때 OVerload Resolution이 실시되어서 실제로 호출될 함수를 결정한다."

    다른 말로 하면 컴파일 과정 속에서 같은 이름을 가진 몇 가지의 함수가 존재한다면 함수 호출 시에 어떤 함수를 실제로 호출할 지 결정한다는 뜻이 됩니다. 이렇게 쉬운 말을!

    Overload Set이라는 개념이 등장을 하는데 이는 별 것 아닙니다. Overload resolution이 실시되기 바로 직전에는 ADL이나 Template Argument deduction등으로 모아서 만든 Set of Candidate functions를 만듭니다. 이것은 실행될 함수들의 후보를 모아놓은 일종의 논리적인 집합입니다. 이것을 Overload Set이라고 부릅니다. 만약, 어떤 후보 함수가 생성자(Ctor)나 파괴자(Desctructor)가 아닌 member function이라면(Static이든 non-static이든 상관없습니다.) 이 함수는 마치 추가적인 Parameter[Implicit Object Parameter]가 있는 것처럼 취급을 받는데 이 parameter가 어떤 object로부터 호출된 것인지 나타냅니다. 그래서 이 implicit object parameter는 실질적인 parameter의 바로 직전에 옵니다. 이런 규칙은 해당 함수가 호출된 Object에도 적용이 되어서 implied object argument라는 형태로 Argument list에 추가됩니다.

    이로써 SFINAE의 첫 문장인 'Function template의 overload resolution이 실시되는 동안에 적용되는 규칙이다.'라는 말을 이해할 수 있게 되었습니다. 그러면 Substituting the explicitly specified of deduced type for the template parameter lists라는 문장에서 Substituting한다는 말은 무슨 말일까요? 이는 SFINAE에서 첫 글자를 담당하고 있습니다.

    'C++' 카테고리의 다른 글

    [ Anything ] - Iterator - 2  (0) 2021.09.08
    [ Anything ] - Iterator - 1  (0) 2021.09.07
    [ Anything ] - Permutation and Combination  (0) 2021.08.29
    [1] - Templates - 1  (0) 2021.08.05
    [ Anything ] - Object Slicing  (0) 2021.05.20
Designed by Tistory.