【问题标题】:How to create specialized function for tuple elements by their type如何按类型为元组元素创建专门的函数
【发布时间】:2017-10-27 01:26:50
【问题描述】:

我想写一个函数,对std::tuple 的每个元素执行一个操作,但它是专门的,如果元素是某种类型,它会做一件事,如果它是另一种类型,它会做另一件事。我有可以对每个元素执行相同操作的代码:

template<std::size_t I = 0, class ...Ts>
inline typename std::enable_if<I == sizeof...(Ts), void>::type
tupel_el_func(std::tuple<Ts...> &t) { } // base case: do nothing

template<std::size_t I = 0, class... Ts>
inline constexpr typename std::enable_if<I < sizeof...(Ts), void>::type
tupel_el_func(std::tuple<Ts...> &t) {

    auto el = std::get<I>(t);
    // do thing with el
    tupel_el_func<I+1, Ts...>(t);
}

但是现在我需要专门化这个函数来为特定类型的元素做不同的事情(例如类型char*)。我试过像这样使用std::enable_if,但它不会编译:

template<int N, typename... Ts>
using NthTypeOf = typename std::tuple_element<N, std::tuple<Ts...>>::type;

template<std::size_t I = 0, class... Ts>
inline constexpr typename std::enable_if<I < sizeof...(Ts) && std::is_same<char*, NthTypeOf<I, Ts...>>::value, void>::type
tupel_el_func(std::tuple<Ts...> &t) {
    // function body for when get<I>(t) is a char*
}

template<std::size_t I = 0, class... Ts>
inline constexpr typename std::enable_if<I < sizeof...(Ts) && !std::is_same<char*, NthTypeOf<I, Ts...>>::value, void>::type
tupel_el_func(std::tuple<Ts...> &t) {
    // function body for generic next element
}

感谢您的帮助。

【问题讨论】:

  • 不是 C++14?这很糟糕。
  • 是的,需要使用 C++11 而不是 14
  • 为什么选择 C++11?它需要支持什么编译器?许多有一些 C++14 特性,这使得这更容易。
  • 有趣。我被告知要实现这个“使用 C+11 特性”。没有明确表示不能使用 C++14 特性。我想我实际上已经找到了一种方法来做到这一点......

标签: c++ c++11 tuples variadic-templates template-meta-programming


【解决方案1】:

我建议完全避免递归。

不好的一点是你使用 C++11,所以你不能使用像 std::index_sequence 和 std::make_index_sequence 这样的 C++14 特性。

但是您可以轻松地模拟它们;以

为例
template <std::size_t ...>
struct indexSequence
 { };

template <std::size_t N, std::size_t ... Next>
struct indexSequenceHelper 
 { using type = typename indexSequenceHelper<N-1U, N-1U, Next ... >::type; };

template <std::size_t ... Next>
struct indexSequenceHelper<0U, Next ... >
 { using type = indexSequence<Next ... >; };

template <std::size_t N>
using makeIndexSequence = typename indexSequenceHelper<N>::type;

现在,您可以编写一个函数foo(),它接收一个通用的std::tuple 对象并调用一个辅助函数fooH() 传递元组,并在range 中,索引列表(从零到@987654328 @ 用于 std::tuple 和 N 类型`)

template <typename ... Ts>
void foo (std::tuple<Ts...> const & t)
 { fooH(t, makeIndexSequence<sizeof...(Ts)>{}); }

接下来,无需递归并使用std::get 和索引,您可以实现辅助函数以在元组中的每个元素上执行函数bar()

template <typename ... Ts, std::size_t ... Is>
void fooH (std::tuple<Ts...> const & t, indexSequence<Is...> const &)
 { 
   using unused = int[];

   (void)unused { 0, ((void)bar(std::get<Is>(t)), 0)... };
 }

现在你开发一个通用模板bar()函数

template <typename T>
void bar (T const & t)
 { std::cout << "- generic bar(): " << t << std::endl; }

以及为特定类型(在以下示例中为long)提供尽可能多的特定重载版本

void bar (long l)
 { std::cout << "- bar(), long version: " << l << std::endl; }

以下是完整的 C++11 工作示例

#include <tuple>
#include <iostream>

template <std::size_t ...>
struct indexSequence
 { };

template <std::size_t N, std::size_t ... Next>
struct indexSequenceHelper 
 { using type = typename indexSequenceHelper<N-1U, N-1U, Next ... >::type; };

template <std::size_t ... Next>
struct indexSequenceHelper<0U, Next ... >
 { using type = indexSequence<Next ... >; };

template <std::size_t N>
using makeIndexSequence = typename indexSequenceHelper<N>::type;

template <typename T>
void bar (T const & t)
 { std::cout << "- generic bar(): " << t << std::endl; }

void bar (long l)
 { std::cout << "- bar(), long version: " << l << std::endl; }

template <typename ... Ts, std::size_t ... Is>
void fooH (std::tuple<Ts...> const & t, indexSequence<Is...> const &)
 { 
   using unused = int[];

   (void)unused { 0, ((void)bar(std::get<Is>(t)), 0)... };
 }

template <typename ... Ts>
void foo (std::tuple<Ts...> const & t)
 { fooH(t, makeIndexSequence<sizeof...(Ts)>{}); }

int main ()
 {
   std::tuple<short, int, long, long long> t { 0, 1, 2L, 3LL };

   foo(t);
 }

【讨论】:

  • 最好只实现 index_sequence 和 make_index_sequence ,所以 OP a) 学习如何使用它们,b) 当它们切换到 14 时,他们可以删除它们的实现。
  • @NirFriedman - 嗯...我认为使用完全相同相同的名称不是一个好主意,但是...好点;像往常一样,我选择的名字是可怕的。用更好的(我希望的)名字修改了答案。
【解决方案2】:

您可以使用专门/通用函数编写元素处理函数的逻辑,这些函数为每种类型的特定情况实现逻辑:

template<std::size_t I = 0, class ...Ts>
inline typename std::enable_if<I == sizeof...(Ts), void>::type
tupel_el_func(std::tuple<Ts...> &t) { } // base case: do nothing

template<std::size_t I = 0, class... Ts>
inline constexpr typename std::enable_if<I < sizeof...(Ts), void>::type
tupel_el_func(std::tuple<Ts...> &t) {
    do_logic(get<I>(t));
    tupel_el_func<I+1>(t);
}

然后有专门的/通用的do_logic 函数,如下所示:

template <class T>
void do_logic(T element) {
   // generic logic
}

template <>
void do_logic(char* element) {
   // char* logic
}

【讨论】:

  • 这对我有用(完全解决了我的问题)所以我知道为什么不赞成
  • 你有没有基本情况的递归。
  • 问题陈述中不是基本情况吗?
  • 最好也将它包含在此处,但是好的,很公平,我将删除反对票。也就是说,递归仍然不是最好的方法,请参阅其他答案。编辑:除非您编辑某些内容,否则我无法删除反对票。所以,只需复制并粘贴基本情况,我会删除它:-)。
  • 啊,明白了。我将尝试在不递归的情况下使用您非常彻底的答案。谢谢!
猜你喜欢
  • 1970-01-01
  • 2012-11-22
  • 2019-05-13
  • 2019-06-27
  • 1970-01-01
  • 2019-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多