代码之家  ›  专栏  ›  技术社区  ›  Viatorus

SFINAE:函数模板优化

  •  4
  • Viatorus  · 技术社区  · 8 年前

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

    无效使用了不完整的类型:“class std::tuple_element<0ul,标准::元组<>>'

    问题似乎是,尽管 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,然后计算sizeoftuple-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

    3 回复  |  直到 8 年前
        1
  •  4
  •   skypjack    8 年前

    正如@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==尺寸
    因此 tuple_element_t (让我说)发出了一个超出范围的错误,这就是为什么您会出现编译错误。

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

        2
  •  1
  •   Community CDub    7 年前

    在对的评论中 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
  •  1
  •   max66    8 年前

    如果使用额外的结构,通过部分专用化,可以避免使用 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; }