C++ template that knows its argument's position

Viewed 165

I am new in c++ metaprogramming and few days ago I decided to write a template with variadic parameter pack, that knows in which position in parameter pack stands first int. More precisely I want to have a struct with name GetIntPos and with static constexpr int value which indicates the position of int in parameter pack starting with 1, and 0 if there is no int parameter type in parameter pack. For example

cout<<GetIntPos<long, char, int, long>::value; // must print 3
cout<<GetIntPos<int, int, long>::value; // must print 1

and

cout<<GetIntPos<long, long, char>::value; // must print 0;

How can it be done?

5 Answers

This is actually pretty simple to solve with a template type-trait:

#include <type_traits> // std::integral_constant
#include <cstddef>     // std::size_t

// Recursive base template (specialized below)
template <std::size_t Current, typename T, typename...Types>
struct find_type_aux;

// Specialization: If we find a match
template <std::size_t Current, typename T, typename...Types>
struct find_type_aux<Current,T,T,Types...>
  : std::integral_constant<std::size_t,Current>
{};

// Specialization: If we haven't found a match, but still have some left in the pack
template <std::size_t Current, typename T, typename Type0, typename...Types>
struct find_type_aux<Current,T,Type0, Types...>
  : find_type_aux<Current + 1, Types...> // Strip off first, and search rest. Increment count
{};

// Specialization: If we didn't find a match
template <std::size_t Current, typename T>
struct find_type_aux<Current,T>
  : std::integral_constant<std::size_t,static_cast<std::size_t>(-1)>{};
{};

// The interface: Find's "T" and returns the 0-based index.
template <typename T, typename...Types>
struct find_type : find_type_aux<0u,T,Types...>{};

With a trait like this, the found element will be the 0-based index, and non-found will be static_cast<std::size_t>(-1). 1

Since you mentioned being 1-indexed with 0 for not found, using find_type<int, Types...>::value + 1 will yield either 0 if not found (due to overflow), or a 1-indexed result -- as requested.


1 The reason this is not explicitly defined to be 1-indexed is that the current definition can be reused to find the index of any type in a variadic pack -- and most interfaces that operate with types that contain variadics expect 0-indexing (such as std::get). This can easily be used as the building blocks specifically for int, such as with a variable template:

template <typename...Types>
constexpr auto find_int_v = find_type<int,Types...>::value + 1;

Which then yields the correct answers from:

int main() {
    std::cout << find_int_v<short, long, char> << '\n';
    std::cout << find_int_v<int, short, long, char> << '\n';
    std::cout << find_int_v<short, long, char, int> << '\n';
    return 0;
}

as

0
1
4

This seems to work in my tests:

namespace detail {
    template <int INDEX>
    constexpr int GetIntPosImpl() {
        return 0;
    }

    template <int INDEX, typename T, typename ...Ts>
    constexpr int GetIntPosImpl() {
        if constexpr(std::is_same_v<T, int>) {
            return INDEX;
        }
        else {
            return GetIntPosImpl<INDEX + 1, Ts...>();
        }
    };
}

template <typename ...Ts>
struct GetIntPos {
    static constexpr int value = detail::GetIntPosImpl<1, Ts...>();
};

int main() {
    std::cout << GetIntPos<short, long, char>::value << '\n';
    std::cout << GetIntPos<int, short, long, char>::value << '\n';
    std::cout << GetIntPos<short, long, char, int>::value << '\n';
    return 0;
}

My output:

0
1
4

My not-recursive way (just for fun... or for a code obfuscation context)

#include <iostream>
#include <algorithm>
#include <type_traits>

template <int = 0>
auto GIP_helper (std::index_sequence<>)
   -> std::integral_constant<std::size_t, 0u>;

template <typename ... Ts, std::size_t ... Is>
auto GIP_helper (std::index_sequence<Is...>)
   -> std::integral_constant<std::size_t,
       (1u+std::min({(std::is_same<Ts, int>::value
                      ? Is
                      : sizeof...(Is))...})) % (1u+sizeof...(Is))>;

template <typename ... Ts>
using GetIntPos
   = decltype( GIP_helper<Ts...>(std::index_sequence_for<Ts...>{}) );

int main()
 {
   std::cout << GetIntPos<long, char, int, long>::value << std::endl; 
   std::cout << GetIntPos<int, int, long>::value << std::endl;
   std::cout << GetIntPos<long, long, char>::value << std::endl;
 }

Works starting from C++14.

My recursive way

#include <iostream>
#include <type_traits>

template <std::size_t, typename ...>
struct GIP_helper
   : public std::integral_constant<std::size_t, 0u>
 { };

template <std::size_t N, typename T0, typename ... Ts>
struct GIP_helper<N, T0, Ts...>
   : public GIP_helper<N+1u, Ts...>
 { };

template <std::size_t N, typename ... Ts>
struct GIP_helper<N, int, Ts...>
   : public std::integral_constant<std::size_t, N>
 { };


template <typename... Ts>
struct GetIntPos : public GIP_helper<1u, Ts...>
 { };

int main()
 {
   std::cout << GetIntPos<long, char, int, long>::value << std::endl; 
   std::cout << GetIntPos<int, int, long>::value << std::endl;
   std::cout << GetIntPos<long, long, char>::value << std::endl;
 }

Works starting from C++11.

Here is a solution using Boost.MP11:

#include <boost/mp11/algorithm.hpp>

template<class... TArgs>
constexpr auto get_int_pos() {
    using list_t = boost::mp11::mp_list<TArgs...>;
    using index_t = boost::mp11::mp_find<list_t, int>;
    return index_t{} == boost::mp11::mp_size<list_t>{} ? 0 : 1 + index_t{};    
}

int main() {
    static_assert(0 == get_int_pos<double>());
    static_assert(0 == get_int_pos<long, long, char>());
    static_assert(1 == get_int_pos<int, int, long>());
    static_assert(3 == get_int_pos<long, char, int, long>());
    static_assert(4 == get_int_pos<short, long, char, int>());
}
Related