Open In App

How to Determine if a Type is an STL Container at Compile Time?

Last Updated : 14 Aug, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

In C++, Standard Template Library (STL) provides many containers such as std::vector, std::list, std::map, and more. What if we want to write a generic code that behaves differently when a given type is an STL container? To do this we need to determine at compile time whether a given type is an STL container or not.

In this article, we will learn different methods to determine if a type is an STL container at compile time in C++ using type traits and template metaprogramming techniques.

Methods to Determine if a Type is an STL Container

There are several techniques to determine if a type is an STL container at compile time:

  1. Using std::void_t and SFINAE
  2. Specializing Traits
  3. Using Concepts (C++20)

Let's discuss each method along with examples.

1. Using std::void_t and SFINAE

We can use the SFINAE that stands for Substitution Failure Is Not An Error that allows us to enable or disable template specializations based on whether certain expressions are valid and std::void_t which is a helper type that allows us to create SFINAE checks more easily.

  • We try to detect if a type has a nested type like iterator, which is commonly present in STL containers.
  • If the type has an iterator, our trait will return true, indicating it’s likely an STL container. Otherwise, it will return false.

Example:

C++
// C++ program to check if a type is an STL container using type traits.
#include <iostream>
#include <map>
#include <type_traits>
#include <vector>
using namespace std;

// A type trait to determine if a type is an STL container
template <typename, typename = void_t<>> struct is_stl_container : false_type{
};

template <typename T> struct is_stl_container<T, void_t<typename T::iterator>> : true_type{
};

// Helper variable template
template <typename T> inline constexpr bool is_stl_container_v = is_stl_container<T>::value;

int main(){
    // Check if vector is an STL container
    cout << "Is vector an STL container? " << is_stl_container_v<vector<int>> << endl;

    // Check if map is an STL container
    cout << "Is map an STL container? " << is_stl_container_v<map<int, int>> << endl;

    // Check if int is an STL container
    cout << "Is int an STL container? " << is_stl_container_v<int> << endl;

    return 0;
}


Output

Is vector an STL container? 1
Is map an STL container? 1
Is int an STL container? 0

Explanation: In the above example, is_stl_container is a type trait that checks whether a type has an iterator type. If the type has an iterator, it is likely an STL container, and the trait returns true. Otherwise, it returns false.

2. Specializing Traits

Another method that we can use instead of trying to detect containers generically is explicitly specializing a trait for each STL container type. This approach works well when we have a fixed set of container types we want to check for.

  • Create a base template that defaults to false, meaning the type is not an STL container.
  • Then specialize this template for each STL container type, setting the trait to true.

Example:

C++
// C++ program to check if a type is an STL container using type traits.
#include <iostream>
#include <list>
#include <map>
#include <vector>
using namespace std;

// Base template: assumes T is not an STL container
template <typename T> struct is_stl_container : false_type{
};

// Specializations for specific STL container types
template <typename... Args> struct is_stl_container<vector<Args...>> : true_type{
};

template <typename... Args> struct is_stl_container<list<Args...>> : true_type{
};

template <typename... Args> struct is_stl_container<map<Args...>> : true_type{
};

// Helper variable template
template <typename T> inline constexpr bool is_stl_container_v = is_stl_container<T>::value;

int main()
{
    // Check if vector is an STL container
    cout << "Is vector an STL container? " << boolalpha << is_stl_container_v<vector<int>> << endl;

    // Check if list is an STL container
    cout << "Is list an STL container? " << boolalpha << is_stl_container_v<list<int>> << endl;

    // Check if map is an STL container
    cout << "Is map an STL container? " << boolalpha << is_stl_container_v<map<int, int>> << endl;

    // Check if int is an STL container
    cout << "Is int an STL container? " << boolalpha << is_stl_container_v<int> << endl;
    return 0;
}


Output

Is vector an STL container? true
Is list an STL container? true
Is map an STL container? true
Is int an STL container? false

Explanation: In this example, we explicitly specialize the is_stl_container trait for specific STL containers (std::vector, std::list, std::map). This method gives you fine-grained control over which types are considered STL containers.

3. Using Concepts (C++20)

In C++20 or later, we can also use concepts which allow us to introduce constraints on template parameters directly.

  • Define a Container concept that checks if a type has an iterator type and begin() and end() methods.
  • Use this concept to constrain template parameters, ensuring that only STL containers are accepted.

Example:

C++
// C++ program to check if a type is an STL container using concepts.
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <concepts>
using namespace std;

// Define a concept for STL containers
template <typename T>
concept Container = requires(T t) {
  
   // Type must have an iterator type
    typename T::iterator;  
  
  // Must have a begin() method returning an iterator
    { t.begin() } -> same_as<typename T::iterator>;  
  
  // Must have an end() method returning an iterator
    { t.end() } -> same_as<typename T::iterator>; 
};

int main() {
  
    // Check if vector is an STL container
    cout << "Is vector an STL container? " << boolalpha << Container<vector<int>> << endl;
    
    // Check if list is an STL container
    cout << "Is list an STL container? " << boolalpha << Container<list<int>> << endl;
    
    // Check if map is an STL container
    cout << "Is map an STL container? " << boolalpha << Container<map<int, int>> << endl;
    
    // Check if int is an STL container
    cout << "Is int an STL container? " << boolalpha << Container<int> << endl;

    return 0;
}


Output

Is std::vector an STL container? true
Is std::list an STL container? true
Is std::map an STL container? true
Is int an STL container? false

Explanation: In this example, the Container concept checks if a type has an iterator type and valid begin() and end() methods. This approach is clean, concise, and takes full advantage of C++20's concept feature.

Conclusion

In conclusion, determining whether a type is an STL container at compile time is a important technique in C++ programming. By using methods such as std::void_t with SFINAE, specializing traits, or using C++20 concepts, we can create compile-time checks that enhance type safety, optimize performance, and improve the flexibility of our code.


Next Article
Article Tags :
Practice Tags :

Similar Reads