知道其参数位置的 C++ 模板

C++ template that knows its argument's position

我是 c++ 元编程的新手,几天前我决定编写一个带有可变参数包的模板,它知道参数包中的哪个位置最先 int。更准确地说,我想要一个 struct 名称 GetIntPosstatic constexpr int value 表示int在参数包中的位置从1开始,如果参数包中没有int参数类型则为0。例如

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

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

如何做到?

这实际上很容易用模板解决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...>{};

使用这样的特征,找到的元素将是从 0 开始的索引,non-found 将是 static_cast<std::size_t>(-1)1

由于您提到使用 0 进行 1 索引表示未找到,如果未找到(由于溢出),使用 find_type<int, Types...>::value + 1 将产生 01-索引结果——根据要求。


1 未明确定义为 1 索引的原因是当前定义可重复用于查找可变参数包中任何类型的索引——并且大多数使用包含可变参数的类型的接口都期望 0 索引(例如 std::get)。这可以很容易地用作专门用于 int 的构建块,例如使用变量模板:

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

然后从中得出正确答案:

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;
}

作为

0
1
4

这似乎在我的测试中有效:

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;
}

我的输出:

0
1
4

我的递归方式

#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;
 }

从 C++11 开始工作。

我的 not-recursive 方式(只是为了好玩......或用于代码混淆上下文)

#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;
 }

从 C++14 开始工作。

这是一个使用 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>());
}