【问题标题】:SFINAE: function templates optimationSFINAE:功能模板优化
【发布时间】:2016-09-14 14:11:17
【问题描述】:

我想以某种方式使用成员函数模板迭代一个元组(以便稍后从给定的模板类型T 创建一个新类型的元组)。

但是,没有使用中断条件(函数),所以我得到了这个错误:

不完整类型的无效使用:'class std::tuple_element >'

问题似乎是,即使元组的N == sizestd::tuple_element_t 被评估为N != size,而不是作为 SFINAE 处理。

两个示例都显示了不同的无效解决方案。我哪里错了?

注意:使用is_same 评估的函数被省略以最小化示例。

#include <type_traits>
#include <tuple>

template<typename...Ts>
struct A
{
  using tuple = std::tuple<Ts...>;
  static constexpr std::size_t size = sizeof...(Ts);

  template<typename T, std::size_t N = 0, typename std::enable_if_t<N == size>* = nullptr>
  int get()
  {
    return 0;
  }

  template<typename T, std::size_t N = 0, typename std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
  int get()
  {
    return get<T, N + 1>() - 1;
  }
};

int main()
{
  A<int, float, double, float, float> a;

  return a.get<char>();
}

Live Example 1

#include <type_traits>
#include <tuple>

template<typename...Ts>
struct A
{
  using tuple = std::tuple<Ts...>;
  static constexpr std::size_t size = sizeof...(Ts);

  template<typename T, std::size_t N = 0>
  std::enable_if_t<N == size, int> get()
  {
    return 0;
  }

  template<typename T, std::size_t N = 0>
  std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int> get()
  {
    return get<T, N + 1>() - 1;
  }
};

int main()
{
  A<int, float, double, float, float> a;

  return a.get<char>();
}

Live Example 2

一种解决方法是使用第三个函数来评估直到 sizeof tuple - 2 而不是评估 sizeof tuple - 1,但这真的有必要吗?

#include <type_traits>
#include <tuple>

template<typename...Ts>
  struct A
  {
    using tuple = std::tuple<Ts...>;
    static constexpr std::size_t size = sizeof...(Ts);

    template<typename T, std::size_t N = 0, typename std::enable_if_t<(N == size - 1) && std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
      int get()
    {
      return 1;
    }

    template<typename T, std::size_t N = 0, typename std::enable_if_t<(N == size - 1) && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
      int get()
    {
      return 2;
    }

    template<typename T, std::size_t N = 0, typename std::enable_if_t<(N < size - 1) && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
      int get()
    {
      return get<T, N + 1>() - 1;
    }
  };

int main()
{
  A<int, float, double, float, float> a;

  return a.get<char>();
}

Live Example 3

【问题讨论】:

  • 最后一个索引是size-1,不是size,所以N应该和值size-1比较
  • 我知道,所以最后一个函数(终止)应该是元素大小的一个索引(这是因为 N != size)。如果对于 N

标签: c++ templates tuples c++14 sfinae


【解决方案1】:

正如@PiotrSkotnicki 在 cmets 中对问题所建议的那样,这是您修复后的第二个示例:

#include <type_traits>
#include <tuple>

template<typename...Ts>
struct A
{
  using tuple = std::tuple<Ts...>;
  static constexpr std::size_t size = sizeof...(Ts);

  template<typename T, std::size_t N = 0>
  std::enable_if_t<N == size-1, int>
  get()
  {
    return std::is_same<T, std::tuple_element_t<N, tuple>>::value ? N : 0;
  }

  template<typename T, std::size_t N = 0>
  std::enable_if_t<N != size-1 && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int>
  get()
  {
    return get<T, N + 1>() - 1;
  }
};

int main()
{
  A<int, float, double, float, float> a;
  return a.get<char>();
}

出了什么问题?
考虑以下行:

std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int> get() 

在这种情况下,N 被替换以评估 enable_if 的条件,即使在 N == size 时也是如此(必须替换才能找到 N == size 确实如此)。
因此,tuple_element_t(让我说)发出了超出范围的问题,这就是您收到编译错误的原因。

我只是更新了您的代码以避免在迭代 N 时到达 size。这是一个使用size-1 作为在函数之间切换的值的问题。

【讨论】:

  • 感谢您的回答。它确实解决了这个问题,但不能用于基于使用哪个函数的自动类型返回类型推导(返回 int 只是一个示例)。我应该更清楚这一点。但是,您是对的,问题是 tuple_element 评估的编译时错误。
  • @Viatorus 我添加了another answer,这可能会解决您的问题。告诉我。
【解决方案2】:

this 的评论中回答 OP 说:

它确实解决了这个问题,但不能根据使用的函数自动类型返回类型推导(返回 int 只是一个例子)。我应该更清楚这一点。

它遵循一个最小的工作示例,可能也解决了这个问题。
在这种情况下,在继承和标签调度方面更容易推理,从而减少由于 sfinae 导致的样板。此外,如果需要,可以使用特化来为特定类型引入特定行为。
最后一种情况,即不属于类型列表的类型的情况,也可以在专用函数中轻松处理。

代码如下:

#include <type_traits>
#include <tuple>

template<typename>
struct tag {};

template<typename...>
struct B;

template<typename T, typename... Ts>
struct B<T, Ts...>: B<Ts...> {
    using B<Ts...>::get;

    auto get(tag<T>) {
        return T{};
    }
};

template<>
struct B<> {
    template<typename T>
    auto get(tag<T>) {
        return nullptr;
    }
};

template<typename...Ts>
struct A: private B<Ts...>
{
    template<typename T>
    auto get() {
        return B<Ts...>::get(tag<T>{});
    }
};

int main()
{
  A<int, float, double, float, float> a;
  static_assert(std::is_same<decltype(a.get<char>()), std::nullptr_t>::value, "!");
  static_assert(std::is_same<decltype(a.get<float>()), float>::value, "!");
}

【讨论】:

    【解决方案3】:

    如果使用额外的结构,通过部分特化,可以避免使用std::tuple_element_t

    我的意思是,像

      template <typename T, std::size_t N>
      struct checkType
       { constexpr static bool value
          = std::is_same<T, std::tuple_element_t<N, tuple>>::value; };
    
      template <typename T>
      struct checkType<T, size>
       { constexpr static bool value = false; };
    
      template <typename, std::size_t N = 0>
      std::enable_if_t<N == size, int> get ()
       { return 0; }
    
      template <typename T, std::size_t N = 0>
      std::enable_if_t<(N < size) && ! checkType<T, N>::value, int> get()
       { return get<T, N + 1>() - 1; }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-26
      • 2017-10-06
      • 1970-01-01
      • 2011-06-05
      相关资源
      最近更新 更多