SFINAE:函数模板优化

SFINAE: function templates optimation

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

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

invalid use of incomplete type: 'class std::tuple_element<0ul, std::tuple<> >'

问题似乎是,即使 N == size 的元组,std::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

使用一个额外的结构怎么样,通过部分特化,可以避免使用 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; }

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

#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 作为在函数之间切换的值的问题。

在对 的评论中回答 OP 说:

It does solve the problem but not for automatic type return type deduction based on which function is used (returning int was just an example). I should have been clearer on this.

它遵循一个最小的工作示例,可能也解决了这个问题。
在这种情况下,在继承和标签调度方面进行推理要容易得多,以减少由于 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, "!");
}