Class Templates

Constraining Template Arguments

How can I create a template that only accepts certain types of arguments?

Illustration representing computer hardware

Constraining template arguments is a powerful feature in C++ that allows you to restrict which types can be used with your templates. This can help prevent errors, improve compile-time error messages, and make your code more self-documenting.

There are several ways to achieve this, with the most modern and recommended approach being concepts, introduced in C++20.

Here are the main approaches, starting with the most modern:

1. Concepts (C++20 and later)

Concepts provide a way to specify constraints on template arguments directly in the template declaration.

#include <concepts>
#include <iostream>

// Define a concept
template <typename T>
concept Numeric = std::integral<T>
  || std::floating_point<T>;

// Use the concept in a template
template <Numeric T>
T Add(T a, T b) {
  return a + b;
}

int main() {
  // Works with int
  std::cout << Add(5, 3) << '\n';

  // Works with double
  std::cout << Add(3.14, 2.86) << '\n';
  
  // This would not compile
  Add("Hello", "World");
}
error: 'Add': no matching overloaded function found
note: could be 'T Add(T,T)'
note: the associated constraints are not satisfied

2. SFINAE (Substitution Failure Is Not An Error)

For pre-C++20 code, SFINAE is a common technique to constrain templates.

#include <iostream>
#include <type_traits>

template <
  typename T,
  typename = std::enable_if_t<std::is_arithmetic_v<T>>
>
T Add(T a, T b) {
  return a + b;
}

int main() {
  // Works with int
  std::cout << Add(5, 3) << '\n';

  // Works with double
  std::cout << Add(3.14, 2.86) << '\n';
  
  // This would not compile
  Add("Hello", "World");
}
error: 'Add': no matching overloaded function found
note: could be 'T Add(T,T)'
note: 'T Add(T,T)': could not deduce template argument for '<unnamed-symbol>'

3. Static Assertions

While not as flexible as the above methods, static assertions can be used to provide clear error messages when constraints are violated.

#include <iostream>
#include <type_traits>

template <typename T>
T Add(T a, T b) {
  static_assert(
    std::is_arithmetic_v<T>,
    "Add only works with numeric types"
  );
  return a + b;
}

int main() {
  // Works with int
  std::cout << Add(5, 3) << '\n';

  // Works with double
  std::cout << Add(3.14, 2.86) << '\n';

  // This would cause a static assertion failure
  Add("Hello", "World");
}
error: static_assert failed: 'Add only works with numeric types'

4. Tag Dispatching

This technique uses function overloading and tag types to select different implementations based on type properties.

#include <iostream>
#include <type_traits>

// Implementation for arithmetic types
template <typename T>
T Add(T a, T b, std::true_type) {
  return a + b;
}

// Implementation for non-arithmetic types
// (will cause a compile error)
template <typename T>
T Add(T a, T b, std::false_type) {
  static_assert(
    std::is_arithmetic_v<T>,
    "Add only works with numeric types"
  );

  // Never reached due to the static_assert
  return T{};
}

// Public interface
template <typename T>
T Add(T a, T b) {
  return Add(a, b, std::is_arithmetic<T>{});
}

int main() {
  // Works with int
  std::cout << Add(5, 3) << '\n';

  // Works with double
  std::cout << Add(3.14, 2.86) << '\n';

  // This would cause a compile error
  Add("Hello", "World");
}
error: static_assert failed: 'Add only works with numeric types'

Each of these approaches has its strengths:

  • Concepts provide the clearest syntax and best error messages.
  • SFINAE is widely supported and can be very flexible.
  • Static assertions provide very clear error messages but are less flexible.
  • Tag dispatching can be used to select entirely different implementations based on type properties.

When constraining template arguments, choose the approach that best fits your needs and the C++ version you're targeting. In modern C++, concepts are generally the preferred approach when available.

Answers to questions are automatically generated and may not have been reviewed.

A computer programmer
Part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved