Constraining Template Arguments

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

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.

Class Templates

Learn how templates can be used to create multiple classes from a single blueprint

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Partial Specialization of Class Templates
Is it possible to partially specialize a class template?
Compile-Time Polymorphism with Templates
Can I use templates to implement compile-time polymorphism?
Creating Templates with Variable Number of Parameters
How can I create a template that works with an arbitrary number of type parameters?
Default Values for Non-Type Template Parameters
Is it possible to have default values for non-type template parameters?
Typename vs Class in Template Declarations
What's the difference between typename and class in template parameter declarations?
Creating Class Templates with Multiple Types
How can I create a class template that works with both primitive types and user-defined types?
Best Practices for Naming Template Parameters
What are the best practices for naming template parameters?
Specializing Class Templates
How can I specialize a class template for specific types?
Using Template Parameters in Constructors
Can I use template parameters in the constructor of a class template?
Template Methods in Non-Template Classes
How do I create a template method within a non-template class?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant