템플릿 (C++)

템플릿은 C++에서 제네릭 프로그래밍의 기초입니다. 강력한 형식의 언어인 C++에서는 프로그래머가 명시적으로 선언하거나 컴파일러에서 추론한 특정 형식이 모든 변수에 있어야 합니다. 그러나 많은 데이터 구조와 알고리즘은 작동하는 형식에 관계없이 동일하게 보입니다. 템플릿을 사용하면 클래스 또는 함수의 작업을 정의하고 사용자가 해당 작업에서 작업해야 하는 구체적인 형식을 지정할 수 있습니다.

템플릿 정의 및 사용

템플릿은 사용자가 템플릿 매개 변수에 대해 제공하는 인수를 기반으로 컴파일 시간에 일반 형식 또는 함수를 생성하는 구문입니다. 예를 들어 다음과 같이 함수 템플릿을 정의할 수 있습니다.

template <typename T>
T minimum(const T& lhs, const T& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

위의 코드는 반환 값 및 호출 매개 변수(lhs 및 rhs)가 모두 이 형식인 단일 형식 매개 변수 T를 사용하는 제네릭 함수에 대한 템플릿을 설명합니다. 원하는 형식 매개 변수의 이름을 지정할 수 있지만 규칙에 따라 단일 대문자가 가장 일반적으로 사용됩니다. T는 템플릿 매개 변수 typename 입니다. 키워드(keyword) 이 매개 변수가 형식의 자리 표시자임을 나타냅니다. 함수가 호출되면 컴파일러는 모든 인스턴스를 T 사용자가 지정하거나 컴파일러에서 추론하는 구체적인 형식 인수로 바꿉다. 컴파일러가 템플릿에서 클래스 또는 함수를 생성하는 프로세스를 템플릿 인스턴스화minimum<int>라고 합니다. 템플릿의 minimum<T>인스턴스화입니다.

다른 곳에서는 사용자가 int용으로 특수화된 템플릿의 인스턴스를 선언할 수 있습니다. get_a() 및 get_b()가 int를 반환하는 함수라고 가정합니다.

int a = get_a();
int b = get_b();
int i = minimum<int>(a, b);

그러나 함수 템플릿이며 컴파일러가 a 및 b 인수T 형식을 추론할 수 있으므로 일반 함수처럼 호출할 수 있습니다.

int i = minimum(a, b);

컴파일러가 마지막 문을 발견하면 템플릿에서 모든 T다음으로 int대체되는 새 함수가 생성됩니다.

int minimum(const int& lhs, const int& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

컴파일러가 함수 템플릿에서 형식 추론을 수행하는 방법에 대한 규칙은 일반 함수에 대한 규칙을 기반으로 합니다. 자세한 내용은 함수 템플릿 호출의 오버로드 확인을 참조 하세요.

형식 매개 변수

minimum 위의 템플릿에서 형식 매개 변수 T는 const 및 참조 한정자를 추가하는 함수 호출 매개 변수에 사용될 때까지 어떤 방식으로든 정규화되지 않습니다.

형식 매개 변수 수에는 실질적인 제한이 없습니다. 여러 매개 변수를 쉼표로 구분합니다.

template <typename T, typename U, typename V> class Foo{};

키워드(keyword) class 이 컨텍스트와 typename 동일합니다. 이전 예제를 다음과 같이 표현할 수 있습니다.

template <class T, class U, class V> class Foo{};

줄임표 연산자(...)를 사용하여 임의의 개수의 0개 이상의 형식 매개 변수를 사용하는 템플릿을 정의할 수 있습니다.

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;

모든 기본 제공 또는 사용자 정의 형식을 형식 인수로 사용할 수 있습니다. 예를 들어 표준 라이브러리의 std::vector를 사용하여 형식int, doublestd::string, MyClass, constMyClass*MyClass&등의 변수를 저장할 수 있습니다. 템플릿을 사용할 때의 주요 제한 사항은 형식 인수가 형식 매개 변수에 적용되는 모든 작업을 지원해야 한다는 것입니다. 예를 들어 이 예제에서와 같이 using MyClass 을 호출 minimum 하는 경우:

class MyClass
{
public:
    int num;
    std::wstring description;
};

int main()
{
    MyClass mc1 {1, L"hello"};
    MyClass mc2 {2, L"goodbye"};
    auto result = minimum(mc1, mc2); // Error! C2678
}

연산자에 대한 < 오버로드를 MyClass 제공하지 않으므로 컴파일러 오류가 생성됩니다.

이러한 제한을 적용하는 템플릿을 정의할 수 있지만 특정 템플릿의 형식 인수가 모두 동일한 개체 계층 구조에 속해야 하는 내재된 요구 사항은 없습니다. 개체 지향 기술을 템플릿과 결합할 수 있습니다. 예를 들어 파생*을 벡터<Base*>에 저장할 수 있습니다. 인수는 포인터여야 합니다.

vector<MyClass*> vec;
   MyDerived d(3, L"back again", time(0));
   vec.push_back(&d);

   // or more realistically:
   vector<shared_ptr<MyClass>> vec2;
   vec2.push_back(make_shared<MyDerived>());

요소에 std::vector 적용되는 기본 요구 사항 및 기타 표준 라이브러리 컨테이너 T 는 복사 할당 가능하고 복사 생성 가능해야 합니다 T .

형식이 아닌 매개 변수

C# 및 Java와 같은 다른 언어의 제네릭 형식과 달리 C++ 템플릿은 값 매개 변수라고도 하는 형식이 아닌 매개 변수를 지원합니다. 예를 들어 표준 라이브러리의 std::array 클래스와 유사한 이 예제와 마찬가지로 배열의 길이를 지정하는 상수 정수 계열 값을 제공할 수 있습니다.

template<typename T, size_t L>
class MyArray
{
    T arr[L];
public:
    MyArray() { ... }
};

템플릿 선언의 구문을 확인합니다. 값은 size_t 컴파일 시간에 템플릿 인수로 전달되며 식이어야 합니다 constconstexpr . 다음과 같이 사용합니다.

MyArray<MyClass*, 10> arr;

포인터 및 참조를 포함한 다른 종류의 값은 형식이 아닌 매개 변수로 전달할 수 있습니다. 예를 들어 함수 또는 함수 개체에 대한 포인터를 전달하여 템플릿 코드 내에서 일부 작업을 사용자 지정할 수 있습니다.

형식이 아닌 템플릿 매개 변수에 대한 형식 추론

Visual Studio 2017 이상 및 모드 이상에서 /std:c++17 컴파일러는 다음과 같이 auto선언된 형식이 아닌 템플릿 인수의 형식을 추론합니다.

template <auto x> constexpr auto constant = x;

auto v1 = constant<5>;      // v1 == 5, decltype(v1) is int
auto v2 = constant<true>;   // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>;    // v3 == 'a', decltype(v3) is char

템플릿 매개 변수로서의 템플릿

템플릿은 템플릿 매개 변수일 수 있습니다. 이 예제에서 MyClass2에는 typename 매개 변수 T와 템플릿 매개 변수 Arr의 두 가지 템플릿 매개 변수가 있습니다.

template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
    U u; //Error. U not in scope
};

Arr 매개 변수 자체에는 본문이 없으므로 해당 매개 변수 이름은 필요하지 않습니다. 실제로 본문 내에서 Arr의 typename 또는 클래스 매개 변수 이름을 참조하는 것은 오류입니다MyClass2. 따라서 이 예제와 같이 Arr의 형식 매개 변수 이름을 생략할 수 있습니다.

template<typename T, template<typename, int> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
};

기본 템플릿 인수

클래스 및 함수 템플릿에는 기본 인수가 있을 수 있습니다. 템플릿에 기본 인수가 있는 경우 템플릿을 사용할 때 지정되지 않은 상태로 둘 수 있습니다. 예를 들어 std::vector 템플릿에는 할당자에 대한 기본 인수가 있습니다.

template <class T, class Allocator = allocator<T>> class vector;

대부분의 경우 기본 std::allocator 클래스가 허용되므로 다음과 같은 벡터를 사용합니다.

vector<int> myInts;

그러나 필요한 경우 다음과 같이 사용자 지정 할당자를 지정할 수 있습니다.

vector<int, MyAllocator> ints;

템플릿 인수가 여러 개인 경우 첫 번째 기본 인수 다음의 모든 인수에는 기본 인수가 있어야 합니다.

매개 변수가 모두 기본값인 템플릿을 사용하는 경우 빈 꺾쇠 괄호를 사용합니다.

template<typename A = int, typename B = double>
class Bar
{
    //...
};
...
int main()
{
    Bar<> bar; // use all default type arguments
}

템플릿 특수화

경우에 따라 템플릿이 모든 형식에 대해 정확히 동일한 코드를 정의하는 것은 불가능하거나 바람직하지 않습니다. 예를 들어 형식 인수가 포인터, std::wstring 또는 특정 기본 클래스에서 파생된 형식인 경우에만 실행할 코드 경로를 정의할 수 있습니다. 이러한 경우 해당 특정 형식에 대한 템플릿의 특수화를 정의할 수 있습니다. 사용자가 해당 형식으로 템플릿을 인스턴스화하면 컴파일러는 특수화를 사용하여 클래스를 생성하고 다른 모든 형식에 대해 컴파일러는 보다 일반적인 템플릿을 선택합니다. 모든 매개 변수가 특수화된 전문화는 완전한 전문화입니다. 일부 매개 변수만 특수화된 경우 부분 특수화라고 합니다.

template <typename K, typename V>
class MyMap{/*...*/};

// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization

특수화된 각 형식 매개 변수가 고유한 경우 템플릿에는 다양한 특수화가 있을 수 있습니다. 클래스 템플릿만 부분적으로 특수화될 수 있습니다. 템플릿의 모든 전체 및 부분 특수화는 원래 템플릿과 동일한 네임스페이스에서 선언되어야 합니다.

자세한 내용은 템플릿 특수화를 참조 하세요.