C++ 템플릿 에러 메시지 해석 | "수백 줄 에러" 5분 만에 읽는 법

C++ 템플릿 에러 메시지 해석 | "수백 줄 에러" 5분 만에 읽는 법

이 글의 핵심

C++ 템플릿 에러 메시지 해석에 대한 실전 가이드입니다.

들어가며: “error: 템플릿 에러 300줄… 뭐가 문제죠?"

"std::sort에 잘못된 타입을 넣었더니 에러가 200줄이 나왔어요”

C++ 템플릿을 사용하다 보면 수백 줄의 에러 메시지를 마주하게 됩니다. 특히 STL 알고리즘, 스마트 포인터, 컨테이너를 잘못 사용하면 템플릿 인스턴스화 체인이 길어져 에러 메시지가 폭발적으로 늘어납니다.

std::vector<std::unique_ptr<int>> vec;
std::sort(vec.begin(), vec.end());  // ❌ unique_ptr는 복사 불가

// 에러 메시지: 200줄 이상...
// error: no matching function for call to 'std::sort'
// note: candidate template ignored: requirement '__is_copy_constructible' was not satisfied
// ... (중략 150줄) ...

이 글에서 다루는 것:

  • 템플릿 에러 메시지 구조 이해하기
  • 핵심 정보만 빠르게 찾는 법 (첫 줄, 마지막 줄, required from)
  • 자주 나오는 에러 패턴 10가지와 해결법
  • 컴파일러별 에러 메시지 차이 (GCC, Clang, MSVC)
  • Concepts로 에러 메시지 단축하기
  • 실전 디버깅 전략

목차

  1. 템플릿 에러 메시지 구조
  2. 에러 메시지 읽는 순서
  3. 자주 나오는 에러 패턴 10가지
  4. 컴파일러별 에러 메시지 비교
  5. C++20 Concepts로 에러 단축
  6. 디버깅 전략
  7. 정리

1. 템플릿 에러 메시지 구조

전형적인 템플릿 에러 구조

[1] main.cpp:10:5: error: no matching function for call to 'std::sort'
    std::sort(vec.begin(), vec.end());
    ^~~~~~~~~

[2] /usr/include/c++/11/bits/stl_algo.h:4842:5: note: candidate template ignored
    sort(_RandomAccessIterator __first, _RandomAccessIterator __last)
    ^
    /usr/include/c++/11/bits/stl_algo.h:4842:5: note: because 
    'std::unique_ptr<int>' does not satisfy '__is_copy_constructible'

[3] main.cpp:10:5: note: in instantiation of function template specialization
    'std::sort<std::vector<std::unique_ptr<int>>::iterator>' requested here
    std::sort(vec.begin(), vec.end());
    ^

[4] /usr/include/c++/11/bits/move.h:195:7: note: required from here
    ... (중략 100줄) ...

주의사항: 중간 bits/stl_*.h 수백 줄은 “왜 실패했는지”가 아니라 “어떤 템플릿이 전개되었는지”라서, 첫 errorbecause 줄만 먼저 읽으면 됩니다.

구조 분석:

  1. [1] 실제 에러 위치: 내 코드의 어느 줄에서 에러가 발생했는지
  2. [2] 에러 원인: 왜 실패했는지 (타입 제약 위반, 인자 불일치 등)
  3. [3] 템플릿 인스턴스화 경로: 어떤 템플릿이 인스턴스화되었는지
  4. [4] 내부 구현 스택: STL 내부에서 어떤 경로로 에러가 전파되었는지

핵심: 중간 100줄은 무시해도 됩니다

중요한 정보:

  • 첫 줄: 내 코드의 에러 위치
  • “note: because” 부분: 실제 원인
  • “candidate” 목록: 왜 각 오버로드가 실패했는지

무시해도 되는 부분:

  • “required from here” 반복 (템플릿 인스턴스화 스택)
  • STL 내부 구현 경로 (bits/stl_*.h)

2. 에러 메시지 읽는 순서

1단계: 맨 위 첫 줄 확인

main.cpp:10:5: error: no matching function for call to 'std::sort'
              ^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
              에러 타입  무엇이 문제인지

핵심 정보:

  • 파일:줄:열: main.cpp:10:5 - 내 코드의 정확한 위치
  • 에러 타입: no matching function, ambiguous call, substitution failure
  • 함수 이름: std::sort - 어떤 템플릿 함수가 문제인지

2단계: “note: because” 찾기

note: because 'std::unique_ptr<int>' does not satisfy '__is_copy_constructible'
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
              실제 원인

이 줄이 가장 중요합니다. 템플릿이 왜 인스턴스화에 실패했는지 알려줍니다.

3단계: candidate 목록 확인

note: candidate template ignored: requirement 'is_copy_constructible_v<T>' was not satisfied
note: candidate function not viable: requires 3 arguments, but 2 were provided
note: candidate function template not viable: 'this' argument has type 'const MyClass', but method is not marked const

각 후보가 왜 실패했는지 나와 있습니다. 가장 가까운 후보를 찾아 수정하면 됩니다.

4단계: 중간 스택 무시

note: in instantiation of function template specialization 'std::sort<...>' requested here
note: required from 'std::__sort<...>'
note: required from 'std::__introsort_loop<...>'
... (반복 100줄) ...

이 부분은 건너뛰세요. 템플릿이 어떻게 인스턴스화되었는지 보여주지만, 에러 원인과는 무관합니다.


3. 자주 나오는 에러 패턴 10가지

패턴 1: no matching function for call

가장 흔한 에러: 함수 오버로드를 찾지 못함.

// ❌ 에러 코드
std::vector<std::unique_ptr<int>> vec;
std::sort(vec.begin(), vec.end());

// error: no matching function for call to 'std::sort'
// note: candidate template ignored: 
//   'std::unique_ptr<int>' does not satisfy '__is_copy_constructible'

원인: std::sort는 원소를 복사해야 하는데, unique_ptr는 복사 불가입니다.

해결법:

// ✅ 해결 1: 이동 가능한 타입으로 변경
std::vector<std::shared_ptr<int>> vec;
std::sort(vec.begin(), vec.end());

// ✅ 해결 2: 포인터로 정렬
std::vector<std::unique_ptr<int>> vec;
std::sort(vec.begin(), vec.end(), 
     { return *a < *b; });

패턴 2: ambiguous call to overloaded function

에러: 여러 오버로드가 동일하게 매칭됨.

// ❌ 에러 코드
void print(int x) { std::cout << "int: " << x << '\n'; }
void print(double x) { std::cout << "double: " << x << '\n'; }

print(3.14f);  // float → int? double?

// error: call to 'print' is ambiguous
// note: candidate function
// note: candidate function

원인: floatintdouble 둘 다로 변환 가능합니다.

해결법:

// ✅ 해결 1: 명시적 캐스팅
print(static_cast<double>(3.14f));

// ✅ 해결 2: float 오버로드 추가
void print(float x) { std::cout << "float: " << x << '\n'; }

패턴 3: no type named ‘type’ in struct

에러: 타입 트레이트가 실패함.

// ❌ 에러 코드
template <typename T>
typename T::value_type getFirst(const T& container) {
    return container[0];
}

int arr[5] = {1, 2, 3, 4, 5};
auto x = getFirst(arr);  // int[]에는 value_type이 없음

// error: no type named 'value_type' in 'int[5]'

해결법:

// ✅ 해결 1: std::iterator_traits 사용
template <typename T>
auto getFirst(const T& container) 
    -> typename std::iterator_traits<decltype(std::begin(container))>::value_type {
    return *std::begin(container);
}

// ✅ 해결 2: C++20 Concepts
template <typename T>
    requires requires(T t) { t[0]; }
auto getFirst(const T& container) {
    return container[0];
}

패턴 4: static assertion failed

에러: static_assert가 실패함.

// ❌ 에러 코드
template <typename T>
void process(T value) {
    static_assert(std::is_integral_v<T>, "T must be integral type");
    // ...
}

process(3.14);  // double은 integral이 아님

// error: static assertion failed: T must be integral type

해결법:

// ✅ 올바른 타입 전달
process(42);

// ✅ 또는 제약 완화
template <typename T>
    requires std::is_arithmetic_v<T>  // integral 또는 floating_point
void process(T value) {
    // ...
}

패턴 5: cannot convert from ‘X’ to ‘Y’

에러: 템플릿 인자 타입 불일치.

// ❌ 에러 코드
template <typename T>
void print(const std::vector<T>& vec) {
    for (const T& x : vec) {
        std::cout << x << '\n';
    }
}

std::vector<int> vec = {1, 2, 3};
print<double>(vec);  // vector<int>를 vector<double>로 취급

// error: cannot convert from 'const std::vector<int>' to 'const std::vector<double>&'

해결법:

// ✅ 타입 추론 사용
print(vec);  // T = int로 자동 추론

// ✅ 또는 올바른 타입 명시
print<int>(vec);

패턴 6: incomplete type is not allowed

에러: 전방 선언만 있고 정의가 없음.

// ❌ 에러 코드
class MyClass;  // 전방 선언만

std::vector<MyClass> vec;  // vector는 완전한 타입 필요

// error: incomplete type 'MyClass' is not allowed

원인: std::vector는 원소의 크기를 알아야 하므로 완전한 타입이 필요합니다.

해결법:

// ✅ 해결 1: 정의 포함
#include "MyClass.h"
std::vector<MyClass> vec;

// ✅ 해결 2: 포인터 사용 (전방 선언만으로 가능)
std::vector<MyClass*> vec;

// ✅ 해결 3: unique_ptr 사용
std::vector<std::unique_ptr<MyClass>> vec;

패턴 7: template argument deduction failed

에러: 템플릿 인자를 추론할 수 없음.

// ❌ 에러 코드
template <typename T>
void process(T value, T other) {
    // ...
}

process(42, 3.14);  // T = int? double?

// error: no matching function for call to 'process'
// note: candidate template ignored: deduced conflicting types for parameter 'T'

해결법:

// ✅ 해결 1: 타입 명시
process<double>(42, 3.14);

// ✅ 해결 2: 두 개의 템플릿 인자
template <typename T1, typename T2>
void process(T1 value, T2 other) {
    // ...
}

// ✅ 해결 3: 공통 타입 사용
template <typename T1, typename T2>
void process(T1 value, T2 other) {
    using Common = std::common_type_t<T1, T2>;
    Common a = value;
    Common b = other;
    // ...
}

패턴 8: invalid operands to binary expression

에러: 연산자를 사용할 수 없는 타입.

// ❌ 에러 코드
template <typename T>
T add(T a, T b) {
    return a + b;
}

struct MyClass {};
MyClass obj1, obj2;
auto result = add(obj1, obj2);  // MyClass에 operator+ 없음

// error: invalid operands to binary expression ('MyClass' and 'MyClass')

해결법:

// ✅ 해결 1: operator+ 정의
struct MyClass {
    int value;
    MyClass operator+(const MyClass& other) const {
        return {value + other.value};
    }
};

// ✅ 해결 2: Concepts로 제약
template <typename T>
    requires requires(T a, T b) { a + b; }
T add(T a, T b) {
    return a + b;
}

패턴 9: member access into incomplete type

에러: 전방 선언된 타입의 멤버 접근.

// ❌ 에러 코드
class MyClass;

template <typename T>
void print(const T& obj) {
    std::cout << obj.value << '\n';  // MyClass 정의 필요
}

MyClass obj;
print(obj);

// error: member access into incomplete type 'MyClass'

해결법:

// ✅ 정의 포함
#include "MyClass.h"

template <typename T>
void print(const T& obj) {
    std::cout << obj.value << '\n';
}

패턴 10: too many template arguments

에러: 템플릿 인자 개수 불일치.

// ❌ 에러 코드
std::vector<int, std::allocator<int>, int> vec;  // 인자 3개

// error: too many template arguments for class template 'vector'

해결법:

// ✅ 올바른 인자 개수
std::vector<int> vec;  // 기본 할당자 사용
std::vector<int, std::allocator<int>> vec2;  // 명시적 할당자

4. 컴파일러별 에러 메시지 비교

동일한 에러, 다른 메시지

// 에러 코드
template <typename T>
void print(T value) {
    std::cout << value.name << '\n';  // int에는 name 없음
}

print(42);

GCC 에러 메시지

main.cpp: In instantiation of 'void print(T) [with T = int]':
main.cpp:10:5:   required from here
main.cpp:5:23: error: request for member 'name' in 'value', which is of non-class type 'int'
    5 |     std::cout << value.name << '\n';
      |                       ^~~~

특징: 중간 정도 길이, “required from here”로 호출 경로 표시.

Clang 에러 메시지

main.cpp:5:23: error: member reference base type 'int' is not a structure or union
    std::cout << value.name << '\n';
                 ~~~~~ ^
main.cpp:10:5: note: in instantiation of function template specialization 'print<int>' requested here
    print(42);
    ^

특징: 가장 읽기 쉬움. ^ 기호로 정확한 위치 표시, 간결한 메시지.

MSVC 에러 메시지

main.cpp(5): error C2228: left of '.name' must have class/struct/union
main.cpp(5): note: type is 'int'
main.cpp(10): note: see reference to function template instantiation 'void print<int>(T)' being compiled
        with
        [
            T=int
        ]

특징: 가장 장황함. 템플릿 인자를 별도 블록으로 표시.

권장: Clang을 보조 컴파일러로 사용

# 주 컴파일러가 GCC/MSVC여도, 에러 확인용으로 Clang 사용
clang++ -std=c++17 -fsyntax-only main.cpp

# 또는 Compiler Explorer (godbolt.org) 사용

5. C++20 Concepts로 에러 단축

Before: 템플릿 에러 (50줄)

// C++17 이전
template <typename T>
void process(const std::vector<T>& vec) {
    std::sort(vec.begin(), vec.end());  // const vector는 정렬 불가
}

// error: no matching function for call to 'std::sort'
// note: candidate template ignored: ...
// ... (중략 40줄) ...

After: Concepts 에러 (5줄)

// C++20
template <typename T>
    requires std::sortable<typename std::vector<T>::iterator>
void process(std::vector<T>& vec) {  // const 제거
    std::sort(vec.begin(), vec.end());
}

// error: constraints not satisfied for 'process<int>'
// note: because 'std::vector<int>::const_iterator' does not satisfy 'sortable'

개선점: 에러 메시지가 10배 이상 짧아지고, 원인이 명확합니다.

Concepts 적용 예제

#include <concepts>
#include <vector>
#include <algorithm>

// 제약 조건 명시
template <typename T>
    requires std::integral<T>
T add(T a, T b) {
    return a + b;
}

// 사용
add(1, 2);      // ✅ OK
add(1.5, 2.5);  // ❌ error: constraints not satisfied
                //    note: because 'double' does not satisfy 'integral'

에러 메시지가 한 줄로 끝납니다!


6. 디버깅 전략

전략 1: 이진 탐색으로 원인 좁히기

// 복잡한 템플릿 코드
template <typename T>
void complex(const T& value) {
    auto result = transform(value);
    process(result);
    output(result);
}

// 에러가 나면: 각 단계를 분리해서 테스트
template <typename T>
void complex(const T& value) {
    auto result = transform(value);
    // 여기까지 컴파일되나? → 주석 처리하며 확인
    
    // process(result);
    // output(result);
}

전략 2: 타입 출력으로 확인

// 타입이 뭔지 모를 때
template <typename T>
void debug_type(T value) {
    // 컴파일 에러로 타입 출력
    typename T::this_will_fail;
    
    // 또는 C++20
    static_assert(false, typeid(T).name());
}

// 실행 시 타입 확인
#include <typeinfo>
std::cout << typeid(value).name() << '\n';

전략 3: 단순화된 테스트 케이스

// 복잡한 코드에서 에러가 나면
// 최소 재현 코드(Minimal Reproducible Example) 작성

// Before: 복잡한 코드
template <typename T, typename U, typename V>
auto complex_function(T a, U b, V c) -> decltype(a + b * c) {
    // 100줄 로직
}

// After: 최소 재현
template <typename T>
auto simple(T a, T b) -> decltype(a + b) {
    return a + b;
}

// 이것만 테스트해서 에러 원인 파악

전략 4: 컴파일러 옵션 활용

# GCC: 템플릿 인스턴스화 깊이 제한
g++ -ftemplate-backtrace-limit=5 main.cpp

# Clang: 에러 메시지 색상 강조
clang++ -fcolor-diagnostics main.cpp

# MSVC: 에러 메시지 간소화
cl /diagnostics:caret main.cpp

전략 5: Concepts로 사전 검증

// 템플릿 사용 전에 제약 확인
template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
};

template <Addable T>
T add(T a, T b) {
    return a + b;
}

// 잘못된 타입은 즉시 에러
struct NoAdd {};
add(NoAdd{}, NoAdd{});  // error: constraints not satisfied

실전 사례 분석

사례 1: STL 알고리즘 타입 불일치

에러 코드:

std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
std::vector<int> ids = {1, 2, 3};

// ❌ 타입 불일치
std::transform(names.begin(), names.end(), ids.begin(), ids.begin(),
     {
        return name + std::to_string(id);
    });

// error: no matching function for call to 'std::transform'
// note: candidate template ignored: deduced conflicting types for parameter 'OutputIterator'

문제: std::transform의 출력 반복자가 ids.begin()인데, 결과 타입은 std::string입니다.

해결:

// ✅ 올바른 코드
std::vector<std::string> results;
std::transform(names.begin(), names.end(), ids.begin(), 
    std::back_inserter(results),
     {
        return name + std::to_string(id);
    });

사례 2: 중첩 템플릿 타입 추론 실패

에러 코드:

// ❌ 에러 코드
template <typename Container>
void printFirst(const Container& c) {
    typename Container::value_type::iterator it;  // 중첩 타입
    // ...
}

std::vector<int> vec = {1, 2, 3};
printFirst(vec);

// error: no type named 'iterator' in 'int'

문제: int에는 iterator가 없습니다.

해결:

// ✅ 올바른 코드
template <typename Container>
void printFirst(const Container& c) {
    typename Container::iterator it = c.begin();
    // ...
}

사례 3: SFINAE 실패

에러 코드:

// ❌ 에러 코드
template <typename T>
auto getSize(const T& container) -> decltype(container.size()) {
    return container.size();
}

int arr[5];
auto s = getSize(arr);  // 배열에는 size() 없음

// error: no matching function for call to 'getSize'
// note: candidate template ignored: substitution failure

해결:

// ✅ 해결 1: std::size 사용 (C++17)
#include <iterator>
auto s = std::size(arr);  // 배열도 지원

// ✅ 해결 2: 오버로드 추가
template <typename T, size_t N>
size_t getSize(const T (&arr)[N]) {
    return N;
}

에러 메시지 패턴 인식

”note: candidate” 읽는 법

note: candidate function template not viable: requires 2 arguments, but 1 was provided
      ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^ ^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      후보      함수     템플릿   불가능    이유

패턴:

  • not viable: 이 후보는 사용 불가
  • requires X arguments, but Y were provided: 인자 개수 불일치
  • 'this' argument has type 'const T': const 불일치
  • no known conversion from 'X' to 'Y': 타입 변환 불가

”note: because” 읽는 법

note: because 'std::unique_ptr<int>' does not satisfy 'is_copy_constructible'
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
              실제 원인 (이 부분만 읽으면 됨)

핵심: because 뒤의 내용이 진짜 원인입니다.

”required from” 읽는 법

note: in instantiation of function template specialization 'std::sort<...>' requested here
note: required from 'std::__sort<...>'
note: required from 'std::__introsort_loop<...>'

의미: 템플릿 인스턴스화 체인. 건너뛰어도 됩니다.


도구 활용

Compiler Explorer (godbolt.org)

1. godbolt.org 접속
2. 에러 코드 붙여넣기
3. 컴파일러 선택 (Clang 권장)
4. 에러 메시지 확인

장점:

  • 여러 컴파일러로 동시 테스트
  • Clang의 깔끔한 에러 메시지 확인
  • 온라인에서 빠르게 테스트

Clang-Tidy

# 템플릿 관련 경고 활성화
clang-tidy main.cpp -- -std=c++17

# 체크 항목
# - bugprone-forwarding-reference-overload
# - readability-inconsistent-declaration-parameter-name

C++ Insights (cppinsights.io)

템플릿이 어떻게 인스턴스화되는지 확인

예제:

// 입력
template <typename T>
T add(T a, T b) { return a + b; }

auto x = add(1, 2);

// 출력 (인스턴스화된 코드)
int add<int>(int a, int b) { return a + b; }
int x = add<int>(1, 2);

정리

에러 메시지 읽는 3단계

  1. 첫 줄: 내 코드의 어느 줄에서 에러가 났는지
  2. “note: because”: 왜 실패했는지 (실제 원인)
  3. “candidate” 목록: 각 오버로드가 왜 안 맞는지

자주 나오는 에러 요약

에러 메시지의미해결법
no matching function오버로드를 찾지 못함타입·인자 개수 확인
ambiguous call여러 오버로드가 동일하게 매칭명시적 캐스팅 또는 오버로드 추가
no type named 'type'타입 트레이트 실패SFINAE 또는 Concepts 사용
static assertion failedstatic_assert 실패제약 조건 확인
cannot convert타입 변환 불가올바른 타입 전달
incomplete type전방 선언만 있음정의 포함 또는 포인터 사용
deduction failed템플릿 인자 추론 실패명시적 타입 지정
invalid operands연산자 미정의operator 오버로드

핵심 규칙

  1. 첫 줄과 “because”만 읽으세요 (중간 100줄은 무시)
  2. Clang으로 에러를 확인하세요 (가장 읽기 쉬움)
  3. Concepts를 사용하세요 (C++20, 에러 메시지 10배 단축)
  4. 최소 재현 코드를 만드세요 (복잡한 코드에서 에러 원인 분리)
  5. candidate 목록을 확인하세요 (가장 가까운 오버로드 찾기)

구현 체크리스트

템플릿 에러를 빠르게 해결하기 위한 체크리스트:

  • 에러 메시지의 첫 줄에서 내 코드의 위치를 확인했는가?
  • “note: because” 부분에서 실제 원인을 찾았는가?
  • candidate 목록에서 가장 가까운 오버로드를 확인했는가?
  • 타입이 제약 조건을 만족하는지 확인했는가?
  • Clang으로 에러 메시지를 다시 확인했는가?
  • 최소 재현 코드로 문제를 단순화했는가?
  • C++20 Concepts 사용을 고려했는가?

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++20 Concepts | 템플릿 에러 메시지를 읽기 쉽게 만드는 방법
  • C++ 템플릿 기초 | 제네릭 프로그래밍 초보자 가이드
  • C++ SFINAE | “Substitution Failure Is Not An Error” 가이드
  • C++ 컴파일러 비교 | GCC vs Clang vs MSVC 에러 메시지 차이

자주 묻는 질문 (FAQ)

Q. 에러 메시지를 텍스트 파일로 저장하려면?

A. 컴파일러 출력을 리다이렉트하세요.

# GCC/Clang
g++ main.cpp 2> error.txt

# MSVC (Visual Studio)
# 출력 창에서 우클릭 → "모두 복사"

Q. 템플릿 에러를 컴파일 타임에 더 일찍 잡으려면?

A. static_assertConcepts를 함께 사용하세요.

template <typename T>
void process(T value) {
    static_assert(std::is_integral_v<T>, "T must be integral");
    // ...
}

Q. 외부 라이브러리의 템플릿 에러는 어떻게 읽나요?

A. 내 코드의 호출 지점을 먼저 찾으세요. “required from here”를 역순으로 따라가면 내 코드가 나옵니다.

/usr/include/boost/...  ← 라이브러리 내부 (무시)
note: required from here
/usr/include/boost/...  ← 라이브러리 내부 (무시)
note: required from here
main.cpp:42:  ← 여기! 내 코드

고급 주제: 템플릿 메타프로그래밍 에러

SFINAE 에러 패턴

// SFINAE: Substitution Failure Is Not An Error
template <typename T>
auto getSize(const T& container) -> decltype(container.size()) {
    return container.size();
}

template <typename T, size_t N>
size_t getSize(const T (&arr)[N]) {
    return N;
}

// 사용
std::vector<int> vec;
getSize(vec);  // ✅ 첫 번째 오버로드 선택

int arr[5];
getSize(arr);  // ✅ 두 번째 오버로드 선택 (첫 번째는 SFINAE로 제외)

enable_if 에러

// ❌ 에러 코드
template <typename T, 
          typename = std::enable_if_t<std::is_integral_v<T>>>
void print(T value) {
    std::cout << value << '\n';
}

print(3.14);  // double은 integral이 아님

// error: no matching function for call to 'print'
// note: candidate template ignored: requirement 'is_integral_v<double>' was not satisfied

해결: Concepts로 대체 (C++20).

// ✅ C++20
template <std::integral T>
void print(T value) {
    std::cout << value << '\n';
}

마치며

템플릿 에러 메시지는 처음에는 압도적이지만, 읽는 패턴을 익히면 5분 안에 원인을 찾을 수 있습니다.

핵심 전략:

  1. 첫 줄과 “because”만 읽기 (중간은 무시)
  2. Clang으로 에러 확인 (가장 읽기 쉬움)
  3. Concepts 사용 (C++20, 에러 메시지 10배 단축)
  4. 최소 재현 코드 작성 (복잡한 코드 단순화)

이 가이드를 북마크해 두고, 템플릿 에러가 나올 때마다 참고하세요. 에러 메시지를 읽는 능력은 C++ 숙련도를 크게 높여줍니다.

다음 단계: 템플릿 에러를 해결했다면, C++20 Concepts 완벽 가이드에서 더 안전한 템플릿 코드 작성법을 배워보세요.


관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |