2017년 3월 31일 금요일

C++ Special Member Functions

 Special Member Function이라함은 컴파일러가 필요할 때 직접 생성해주는 멤버 함수를 말한다. 즉 아래 함수들이 Special Member Function들이다.
  • default constructor
  • destructor
  • copy constructor
  • copy assignment operator
  • move constructor
  • move assignment operator
 이 함수들은 사용자가 정의하지 않은 경우 컴파일러가 기본 구현을 제공하는 함수들이다. 하지만 항상 기본 구현이 제공되는 것은 아니고 몇 가지 조건이 만족될 때만 기본 구현을 제공한다. 이번 글에서는 그 규칙에 대해서 정리해보고자 한다.

1. 기본 생성자(default constructor)

 클래스에 생성자가 하나도 정의되어 있지 않은 경우에만 기본 생성자를 제공한다. 기본 생성자(default constructor)는 인자가 하나도 없는 생성자를 말한다. 클래스의 생성자가 단 하나도 정의되지 않은 경우에는 컴파일러가 기본 생성자를 제공하는데 제공되는 기본 생성자의 구체적인 구현은 각 멤버의 기본 생성자가 호출되는 것과 같다. 따라서 컴파일러가 생성하는 기본 생성자를 이용하려면 클래스의 모든 멤버들도 역시 기본 생성자를 제공해야 한다는 조건이 하나 더 붙는다.

2. 소멸자(destructor)

 클래스에 소멸자가 정의되어있지 않으면 컴파일러가 자동으로 기본 구현을 제공한다. 상속받은 부모 클래스의 소멸자가 virtual인 경우에는 마찬가지로 virtual로 선언된 소멸자를 제공하고 그렇지 않은 경우에는 non-virtual이다. 그리고 디폴트로 noexcept 소멸자를 제공한다.

3. 복사 생성자(copy constructor)

 클래스에 복사 생성자(copy constructor)가 정의되어 있지 않으면 컴파일러가 기본 구현을 제공한다. 하지만 제공되는 구현이 경우에 따라 다르다.
  1. 이동 생성자 또는 이동 대입 연산자가 선언되어 있으면 복사 생성자는 delete된 선언이 제공된다.
  2.  이동 생성자 또는 이동 대입 연산자가 선언되어 있고 복사 생성자가 선언되지 않았을 때는 컴파일러가 복사 생성자를 delete로 선언한다. 즉, delete된 구현을 제공하는 것이다. 이 상태에서 복사 생성자를 호출하는 코드가 어딘가에 존재한다면 컴파일러는 delete된 함수를 호출한다는 에러를 발생시킬 것이다.
  3. 소멸자 또는 복사 대입 연산자가 선언되어있으면 컴파일러가 복사 생성자를 제공하지만 이 기능은 deprecated되었다.
  4.  뭔가 말이 안되는 것 같지만 설명을 해 보자면, 이런 경우 컴파일러가 각 멤버를 복사 생성하는 구현을 제공하긴 하지만 이렇게 기본 구현을 제공하는 기능은 deprecated되었다는 것이다. 이런 저런 환경에서 개발을 하다보면 오래된 API들이 deprecated된 경우를 볼 수 있을 것이다. 호환성때문에 존재하지만 언젠가는 사라질 수 있는 API들을 그런 상태로 선언해 놓는다. 마찬가지로 이러한 컴파일러의 기능도 C++표준에서는 사용하지 말도록 권고하는 것이며 직접 구현을 제공할 것을 권고하는 것이다.
  5. 그 외에는 복사 생성자가 선언되지 않았다면 컴파일러가 기본 구현을 제공한다.

4. 복사 대입 연산자(copy assignment operator)

 클래스에 복사 대입 연산자(copy assignment operator)가 정의되어 있지 않으면 컴파일러가 기본 구현을 제공한다. 복사 생성자와 복사 대입 연산자가 서로 대칭될 뿐 같은 규칙으로 제공된다.
  1. 이동 생성자 또는 이동 대입 연산자가 선언되어 있으면 복사 대입 연산자는 delete된 선언이 제공된다.
  2.  이 규칙은 복사 생성자와 같다.
  3. 소멸자 또는 복사 생성자가 선언되어 있으면 컴파일러가 복사 대입 연산자를 제공하지만 이 기능은 deprecated되었다.
  4.  복사 생성자 대신 복사 대입 연산자가 되었을 뿐 규칙은 복사 생성자의 경우와 같다. 즉 제공은 되지만 사용을 하지 않는 게 좋다.
  5. 그 외에는 복사 대입 연산자가 선언되지 않았다면 컴파일러가 기본 구현을 제공한다.

5. 이동 생성자(move constructor)

 이동 생성자(move constructor)는 소멸자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자가 모두 선언되지 않았을 때만 기본 구현이 제공된다. 기본 구현은 각 멤버별로 이동 생성자가 호출되는 것과 같다.

6. 이동 대입 연산자(move assignment operator)

 이동 대입 연산자(move assignment operator)는 소멸자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자가 모두 선언되지 않았을 때만 기본 구현이 제공된다. 즉, 이동 생성자와 같은 규칙이다. 기본 구현은 각 멤버별로 이동 대입 연산자가 호출되는 것과 같다.


 C++98 시대의 C++ 개발자들에게는 3의 규칙이라는 것이 있어서 소멸자, 이동 생성자, 이동 대입 연산자 중에 하나라도 직접 선언했다면 나머지 두 개도 직접 선언해야 한다는 암묵적 규칙이 있었다. 3개 함수중에 하나라도 기본 제공되는 구현이 적당하지 않다면 나머지 2개도 적당하지 않을 확률이 매우 높기 때문에 생긴 규칙이다. 하지만 위 규칙에서 보듯이 암묵적일 뿐 컴파일러 강제사항은 아니다.

 C++11이 되면서 move semantics 개념이 도입되어 이동 연산이 추가되고 3의 규칙은 이동 함수들에도 확장되었다. (5의 규칙?) 하지만 과거의 코드들과 호환성을 유지해야 할 필요성 때문에 이동 함수들만 5개 함수들 중 하나라도 선언되어 있으면 자동 생성되지 않도록 결정되었고 나머지는 거의 그대로 유지가 되고 있다. 복사 함수들이 이동 함수들이 선언된 경우에는 delete로 처리되는 것도 3의 규칙의 확장선 상의 규칙이며 C++11에서 추가되었는데 이것은 이동 함수들이 선언된 경우이므로 C++98 시대의 코드에는 영향을 미치지 않기 때문이다. 3의 규칙과 달리 확장된 규칙들은 컴파일러 강제사항이기 때문에 직접 처리할 수 밖에 없다.

 그렇다면 virtual 소멸자가 필요해서 소멸자를 선언했을 뿐인데 다른 모든 special member function들의 기본 구현을 이용할 수 없게 된다면 어떨까? 이 경우 개발자는 확실히 이것이 불편하다고 느낄 것이다. 이런 경우를 위해서 기본 구현을 간단히 선언할 수 있는 방법이 있다.
class Sample {
public:
  virtual ~Sample() = default; // default destructor

  Sample(const Sample&) = default;            // default copy constructor
  Sample& operator=(const Sample&) = default; // default copy assignment

  Sample(Sample&&) = default;            // default move constructor
  Sample& operator=(Sample&&) = default; // default move assignment

 
  ...
};
함수의 구현을 제공하는 대신 선언부에 '= default'를 붙여주면 컴파일러가 제공하는 기본 구현이 그대로 적용된다. 위의 규칙 중 3번, 4번에서 deprecated된 기능을 사용하지 않고 기본 구현을 사용하는 간단한 방법으로 이렇게 default 선언을 사용하면 사용자가 직접 함수를 선언한 것이 되므로 deprecated된 구현을 사용하는게 아니며 추후에 생길 수 있는 문제를 피할 수 있다. 또한 default 구현을 이용하겠다는 뜻으로 이렇게 명시적으로 선언하는 것은 좋은 습관이다.

2017년 3월 9일 목요일

C++ deleted functions (함수의 삭제 선언)

 C++11에 새롭게 추가된 함수의 삭제(delete)에 대해서 정리해보려고 한다. 어떤 함수를 삭제(delete)시키면 그 함수에 대한 어떤 호출도 불가능하게 된다. 물론 함수를 사용하지 못하게 할 거라면 그냥 함수를 작성하지 않으면 된다. 하지만 문제가 발생하는 함수들은 컴파일러가 자동으로 생성해주는 함수들이다. C++ 컴파일러는 클래스의 생성자, 소멸자, 대입연산자 등을 필요에 의하여 자동으로 생성해주는 기능이 있다. 하지만 개발자는 일부러 그런 함수들을 사용하지 못하게 하려고 만들지 않았는데 컴파일러가 알아서 생성해 준다면 꽤 귀찮은 문제가 된다. 이럴 때 사용하는 방법이 함수를 delete시키는 것이다.

 C++11 이전에는 그런 메소드를 private으로 선언함으로써 다른 개발자들이 사용할 수 없도록 하였으나 C++11에서는 아래 예와 같이 함수를 delete시키는 방법을 제공해 준다.
class NoCopyClass {
public:
    ...
    NoCopyClass(const NoCopyClass&) = delete;
    NoCopyClass& operator=(const NoCopyClass&) = delete;
    ...
};
 복사 생성자와 복사 대입 연산자를 삭제함으로써 클래스의 객체를 복사할 수 없게하려고 한다는 것을 짐작할 수 있다.(이렇게 의도가 전달되는 것은 좋은 일이다.) 이런 생성자와 연산자들은 컴파일러가 기본 구현을 제공하는 것들이지만 개발자가 직접 삭제 함수(deleted function)로 구현한 것이기 때문에 컴파일러도 기본 구현을 제공하지 않고 다른 개발자들이 호출할 수도 없게 되는 것이다.

 참고로 함수의 삭제 선언은 public으로 선언하는 것이 관례이다. 왜냐하면 컴파일러가 함수의 삭제여부 이전에 private여부를 먼저 판단하기 때문에 삭제된 함수에 대한 정확한 메시지를 출력하도록 유도하려면 public으로 선언해야하기 때문이다.

 함수의 삭제는 private으로 선언하는 방법보다 좋은 점이 몇 가지 있는데 클래스의 멤버 함수 외에 모든 함수가 삭제 가능하다는 점이다. 이러한 점을 이용해서 "함수 인자의 암묵적인 변환을 이용한 호출"을 막을 수도 있다. 아래 예에서 isLucky()함수는 정수형을 받는 함수지만 암묵적 변환이 가능한 인자들을 넣고 호출해도 모두 호출이 된다. 하지만 호출을 막고자 하는 타입을 받는 overload 함수를 delete로 선언해버리면 암묵적 변환 전에 완벽하게 매칭되는 함수가 호출되는 것이 우선이므로 삭제된 함수를 호출하게 되고 따라서 호출 불가 판정을 하게 되는 것이다.
bool isLucky(int number);
bool isLucky(double) = delete;

if (isLucky(7)) // 호출 가능
    ...
if (isLucky('a')) // 호출 가능
    ...
if (isLucky(3.5)) // 호출 불가능. 에러!
    ...
 double 타입 인자를 받는 isLucky() 함수가 선언되지 않았으면 3.5는 int형으로 변환되어 호출이 되었을 것이다. 하지만 double 인자 버전이 delete로 선언되었기 때문에 삭제된 함수에 매칭이 되어 호출이 되지 않고 에러를 발생시킨다. 알아두면 가끔 유용하게 쓰일 것 같다.

 함수의 삭제는 템플릿의 특정 특수화(specialization) 버전을 삭제함으로써 그 함수가 생성되는 것을 막는데 이용할 수도 있다. 함수 템플릿은 컴파일러가 추론된 타입에 맞춰서 그때그때 생성하게 되는데 특정 타입에 대한 함수를 생성하지 못하도록 하려고 할 때 미리 사용을 막고자 하는 타입의 특수화 버전을 delete로 선언해버리면 컴파일러는 특수화 함수가 존재하므로 코드를 생성하지 않고 삭제된 함수이므로 호출도 하지 못하게 되는 것이다. 아래 예를 보면 어떤 상황인지 어렵지 않게 이해할 수 있다.
template<typename T>
void processPointer(T* ptr);

template<>
void processPointer<void>(void*) = delete; // void 포인터는 호출 불가!
 void 타입의 특수화 함수를 선언하고 삭제함으로써 그런 함수가 생성되는 걸 막고 당연히 호출도 불가하게 만드는 것이다.

 위와 같이 함수의 delete 선언이 주는 장점은 여러가지가 있지만 사실 가장 큰 장점은 코드의 의도를 분명히 할 수 있다는 데 있다. 특정 메소드를 사용하지 못하게 하려고 private의 선언하는 것보다 delete로 선언하는 게 더 분명하게 의도를 전달할 것이고 그런 코드를 읽게 된다면 무엇을 의도하고 함수를 삭제했는지 파악하는 것도 private으로 선언하는 것보다 훨씬 쉬울 것이다. 코드를 읽기 쉽게, 의도를 분명하게 작성하는 건 기본 중의 기본이고 항상 바람직한 일이다.

2017년 3월 6일 월요일

C++ Unscoped enumerations VS. Scoped enumerations

 C++11에 새로 추가된 것들 중에 enum class라는 것이 있다. 원래부터 있었던 enum과 유사하지만 좀 더 발전된 것이라고 할 수 있겠다. Effective Modern C++에서는 원래 있던 enum을 unscoped enum으로 새로 추가된 enum class를 scoped enum으로 구별해서 부른다. 선언 방식은 아래 예와 같다.
enum Color { black, white, red }; // unscoped enum
int white; // 에러
enum class Color { black, white, red }; // scoped enum
int white; // 문제 없음
 선언 방법에 있어서 scoped enum의 경우 "enum class"를 사용한다는 점이 이전과 달라진 점이다. 두 가지 방식을 scoped와 unscoped로 부르는 이유는 말 그대로 black, white, red의 namespace 범위가 두 경우에 대해 다르게 적용되기 때문이다. 위 예를 가지고 설명해 보자면 unscoped의 경우에는 black, white 등의 이름은 Color와 같은 namespace를 갖는다. 즉, Color라는 이름이 유효한 공간에서는 black, white 등의 이름도 모두 유효하다. 따라서 다른 변수나 함수 이름 등으로 사용할 수가 없다. 하지만 scoped의 경우에는 black, white 등의 이름이 Color 클래스 내부에서만 유효하다. 즉, Color의 중괄호 {} 안에서만 유효하므로 그 밖에서는 그 이름을 다른 변수나 함수 이름으로 사용하는 데 아무런 문제가 없다. 물론 enum값을 사용할 때 Color::black과 같이 사용해야 하는 게 불편할 수도 있지만 내가 보기엔 더 명확하고 좋은 것 같다.

 사실 이렇게 namespace가 오염(?)된다는 것만으로 scoped enum을 사용하라고 한다면 그다지 크게 공감이 가진 않는다. Effective Modern C++에서는 몇 가지 장점을 더 제시하고 있다. 그 중 한 가지가 scoped enum의 경우에는 전방 선언(forward declaration)이 가능하다는 것이다. unscoped enum의 경우에는 전방선언을 할 수 없었지만 scoped enum의 경우 이게 가능해지면서 소스 파일간의 dependency를 줄여서 헤더 파일을 약간 수정함으로써 생기는 많은 소스 코드의 재컴파일을 줄일 수 있다는 장점이 있다. C++로 정말 큰 프로젝트를 수행한다면 빌드 타임을 줄일 수 있다는 점은 큰 메리트가 될 것이다. 하지만 사실 이 점도 나에게는 그렇게 크게 공감가는 부분은 아니다. 대부분 큰 프로젝트를 수행하다보면 어느 정도 작은 크기의 모듈로 나누어지게 마련이고 그러다보면 한 번 빌드하는데 5분이상 걸리는 경우를 만나는 일도 별로 없기 때문이다. 게다가 unscoped enum도 약간의 수고를 더한다면 전방선언이 가능하기 때문에 별로 큰 장점은 아니다. 이 글을 계속 읽어보면 전방선언이 가능하다는 걸 알게 될 것이다.

 Effective Modern C++이 제시하는 장점 중에 내가 생각하는 가장 큰 장점은 타입 체크를 더 강하게 한다는 것이다. Java 같은 언어에서 enum의 타입은 class와 동등하게 엄격한 타입 검사가 적용된다. 하지만 C++에서 (unscoped) enum은 그냥 정수형처럼 사용해도 아무 제지를 받지 않는다. 심지어 float같은 상수와 연산을 해도 암묵적 변환이 자동으로 이루어진다. 물론 이게 편하다고 생각하는 개발자도 있겠지만 적어도 바람직하지 않은 것만은 사실이다. 하지만 C++11에서 추가된 scoped enum의 경우에는 정수형으로의 암묵적 변환이 허용되지 않는다. 필요하다면 개발자가 암묵적이 아닌 직접적인 캐스팅을 해주어야 문제가 없다. 이런 방식이 개발자의 실수를 줄이고 이해하기 쉬운 코드를 만드는데 훨씬 유리하다고 나는 확신한다. Java같은 최신 언어들로부터 느낀 많은 장점들이 C++에도 조금씩 조금씩 적용된다는 건 바람직한 일이 아닐 수 없다.

 많은 개발자들이 신경쓰지 않고 사용했겠지만 enum을 선언할 때 개발자가 직접 기초 타입(underlying type)을 지정할 수 있다. 아래 예에서 보듯이 unscoped와 scoped 둘 다 지정이 가능하다.
 // unscoped enum
enum Status: std::uint32_t { good = 0,
                             failed = 1,
                             incomplete = 100,
                             corrupt = 200,
                             audited = 500,
                             indeterminate = 0xFFFFFFFF
                           };

// scoped enum
enum class Status: std::uint32_t { good = 0,
                                   failed = 1,
                                   incomplete = 100,
                                   corrupt = 200,
                                   audited = 500,
                                   indeterminate = 0xFFFFFFFF
                                 };

 위의 예처럼 기초 타입을 직접 지정하는 경우에는 unscoped enum의 경우에도 전방 선언이 가능하다. 전방 선언이 허용되지 않았던 이유는 enum의 기초 타입이 지정되지 않았을 때는 컴파일러가 enum의 정의를 보고 적당한 크기의 정수형(integral) 타입을 임의로 결정하여 사용하기 때문에 기초 타입이 지정되지 않은 전방 선언 만으로는 그 크기를 알 수 없으므로 전방선언을 허용하지 않았던 것이다. 하지만 scoped enum의 경우에는 직접 지정하지 않았을 때 default로 int형을 사용하게 되어있다. 더 작거나 큰 타입을 사용하고 싶다면 직접 지정해서 사용하면 된다.

 참고로 unscoped enum이 더 편리한 경우를 들어보자면 array나 std::tuple의 인덱스로 사용할 정수형 상수(constant)를 enum으로 선언해놓고 사용할 때이다. 이 경우에는 casting이 필요없는 unscoped enum이 더 사용하기 편리하다. 아래 두 버전의 차이를 보자.
using UserInfo = std::tuple<std::string,  // name
                            std::string,  // email
                            std::size_t>; // reputation

// unscoped enum
enum UserInfoFields { uiName, uiEmail, uiReputation };

UserInfo uInfo;

...

auto val = std::get<uiEmail>(uInfo); // emil 필드의 값 얻기
// scoped enum
enum class UserInfoFields { uiName, uiEmail, uiReputation };

UserInfo uInfo;

...

auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo); // 캐스팅 필요
 굳이 추가 설명을 하지 않아도 unscoped 버전이 훨씬 가독성이 좋고 타이핑해야 할 코드도 짧다. 게다가 이런 식의 사용은 많은 개발자들이 애용하는 방법이기때문에 unscoped enum의 장점을 무시할 수도 없다.

 unscoped enum과 scoped enum을 비교했을 때 정말 치명적인 장단점이 있다고는 생각치 않는다. unscoped enum이 전혀 불편함이 없고 익숙하다면 뭐 그리 대단히 사용하지 말아야 할 이유가 있는 건 아니라는 뜻이다. 하지만 내 의견은 scoped enum을 사용하는 것이 개발자의 실수를 줄이고 코드의 의도를 더 분명히 할 수 있는 더 좋은 습관이라고 생각한다. 그래서 C++표준에 추가된 것일 테고 새로운 세대의 개발자들이 scoped enum을 사용해주길 기대하고 있을 거라 생각한다.

2017년 3월 2일 목요일

C++ type alias : 'using'

 C++11 이전의 C++ 개발자들이 자주 사용하던 using 키워드의 용도는 namespace를 계속 입력하는 게 귀찮아서 STL 사용 시 아래와 같은 선언을 코드의 앞 부분에 추가해주고 "std::"를 생략하기 위함일 것이다. (내 경우에는 그랬다)
using namespace std;
...
 C++11에서 using의 기능을 새롭게 추가하였는데 그것이 타입 별칭(Type alias)을 선언하는 것이다. 기존에 typedef라는 키워드를 이용해서 STL 등에서 제공하는 긴 iterator 타입을 간단하게 선언해서 사용해 본 경험이 많이들 있을 거라고 생각한다. using을 이용한 타입 별칭 선언은 typedef가 하는 일과 마찬가지로 기존 타입을 새로운 이름으로 선언하는 것이다. 아래 함수 포인터 타입을 선언하는 2가지 예를 보면서 어떤 게 더 보기 좋은지 한번 생각해보자.
typedef void (*FP)(int, const std::string&);  // typedef 이용
using FP = void (*)(int, const std::string&); // using 이용
 다른 건 몰라도 함수 포인터 타입을 선언할 때는 using이 훨씬 분명하게 뜻이 전달되는 것 같다. 하지만 이것만이 기존 typedef를 사용하던 습관을 버리고 using을 사용해야 할 이유라면 아마도 typedef가 익숙한 개발자에게는 별로 납득할만한 이유는 되지 못할 것 같다. Effective Modern C++책 에서는 typedef보다는 using을 사용하라고 권장하며 그 이유에 대해서도 설명하고 있는데 여기서 그 이유에 대해 정리를 해 보려고 한다.

 using을 이용한 타입 선언이 typedef와 크게 구별되는 점이 하나 있다. using의 경우에는 템플릿화가 되지만 typedef는 그렇지 못하다는 점이다. 아래 예를 보자.
template<typename T> // std::list<T, MyAlloc<T>>의 별칭은 MyAllocList<T>
using MyAllocList = std::list<T, MyAlloc<T>>;

MyAllocList<Widget> lw; // client code
 C++11에서는 이렇게 using을 이용해서 간단히 T에 따라 변화하는 템플릿 타입의 별칭을 선언할 수 있지만 기존 typedef의 경우에는 템플릿화할 수 없기 때문에 이러한 목적을 이루기 위해서 아래 예와 같은 방법을 사용하였다.
template<typename T> // std::list<T, MyAlloc<T>>의 별칭은 MyAllocList<T>::type
struct MyAllocList {
  typedef std::list<T, MyAlloc<T>> type;
};

MyAllocList<Widget>::type lw; // client code
 템플릿 구조체를 하나 선언해서 그 안에서 typedef를 이용하여 별칭을 선언하는 것이다. 이렇게 함으로써 실제 타입을 사용할 때 "::type"을 덧붙인다는 소소한 불편함을 제외하면 같은 동작을 하도록 할 수 있었다. 하지만 이렇게 선언된 타입은 템플릿 안에서 사용할 때 또 다른 불편함이 발생한다. 아래 예를 보자.
template<typename T>
class Widget {
private:
  typename MyAllocList<T>::type list; // MyAllocList<T>를 멤버로 선언
  ...
};
 MyAllocList::type 타입의 멤버를 선언하는 데 그 앞에 typename이라는 키워드를 써주어야 하는 불편함이 생긴다. 컴파일러가 MyAllocList::type을 만났을 때 이게 타입 이름인지 아니면 MyAllocList의 데이터 멤버인지 알 수가 없기 때문에 typename이라고 앞에 지정함으로써 타입 이름이라고 알려주는 것이다. using을 이용한 타입 별칭 선언은 항상 타입이라는 게 확실하기때문에 아래 예에서 보듯이 typename을 따로 지정하지 않는다.

template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;

template<typename T>
class Widget {
private:
  MyAllocList<T> list;// "typename", "::type"이 없다.
  ...
};

 참고로 한 가지 더 정리하자면 using을 이용한 타입의 별칭 선언은 C++11에서 추가되었지만 STL의 많은 코드는 이전과의 호환성 때문에 이전 방식인 구조체 안에 type을 선언하는 방식을 그대로 유지하고 있다. 하지만 C++14에서는 이전보다 더 간단하게 사용할 수 있도록 같은 기능을 제공하는 템플릿 선언들을 추가로 제공한다. 아래 예에서 보듯이 기존 구조체 이름에 '_t'를 붙여서 같은 기능을 제공하도록 선언하였다.
std::remove_const<T>::type // C++11: const T → T
std::remove_const_t<T>     // C++14 버전

std::remove_reference<T>::type // C++11: T&/T&& → T
std::remove_reference_t<T>     // C++14 버전

std::add_lvalue_reference<T>::type // C++11: T → T&
std::add_lvalue_reference_t<T>     // C++14 버전
 위 예는 헤더파일에서 제공하는 선언들이다. 새롭게 추가된 선언들의 이름이 무엇인지 어렵지 않게 예상할 수 있을 것으로 생각된다.