【问题标题】:Issue with enable_if and multiple conditionsenable_if 和多个条件的问题
【发布时间】:2017-01-08 15:48:11
【问题描述】:

我试图实现一个将泛型类型转换为字符串的函数。整数类型需要使用 std::to_string() 转换,字符串和字符使用 std::string() 和向量,逐个元素地转换为使用其他方法之一的字符串(取决于它们的内容)。

这就是我所拥有的:

//Arithmetic types    

template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type convertToString(const T& t){
    return std::to_string(t);
}

//Other types using string ctor

template<class T>
typename std::enable_if<std::__and_<std::__not_<std::is_arithmetic<T>>::type,
        std::__not_<std::is_same<T, <T,
       std::vector<typename T::value_type, typename T::allocator_type>>::value
       >>>::value, std::string>::type convertToString(const T& t){
    return std::string(t);
}

//Vectors

template<class T>
typename std::enable_if<std::is_same<T, std::vector<typename T::value_type, 
   typename T::allocator_type>>::value, std::string>::type convertToString(const T& t){
    std::string str;
    for(std::size_t i = 0; i < t.size(); i++){
        str += convertToString(t[i]);
    }
    return str;
}

问题是第二个函数没有编译。如何设计第二个函数,使其能够编译(和工作)并且不会产生歧义问题?

【问题讨论】:

    标签: c++ templates c++14 sfinae enable-if


    【解决方案1】:

    Oktalist's 回答解释了为什么你的类型特征不能编译。此外,您不应使用__and___not_。这些是保留的,可以在下一个编译器版本中轻松更改。实现这些特征的您自己的版本很容易(例如,查看conjunction 的可能实现)。

    我会建议一种完全不同的方法。我们可以使用choice&lt;&gt; 来简化这些情况的重载:

    template <int I> struct choice : choice<I+1> { };
    template <> struct choice<10> { };
    

    通过:

    // arithmetic version
    template <class T>
    auto convertToStringHelper(T const& t, choice<0> )
        -> decltype(std::to_string(t))
    {
        return std::to_string(t);
    }
    
    // non-arithmetic version
    template <class T>
    auto convertToStringHelper(T const& t, choice<1> )
        -> decltype(std::string(t))
    {
        return std::string(t);
    }
    
    // vector version
    template <class T, class A>
    std::string convertToStringHelper(std::vector<T,A> const& v, choice<2> )
    {
        // implementation here
    }
    
    template <class T>
    std::string convertToString(T const& t) {
        return convertToStringHelper(t, choice<0>{});
    }
    

    这很好,因为您可以在没有任何 enable_if 的情况下获得所有 SFINAE。

    【讨论】:

    • 这是一个非常优雅的解决方案 :) 我不太喜欢 enable_if 语法,因为它使代码难以阅读。
    • 这是一个非常好的解决方案;我花了一段时间才弄清楚它是如何工作的,直到我意识到它只会分割choice&lt;0&gt;,直到它达到匹配的功能。不过,我确实相信它有一个问题:根据问题,他希望chars 由choice&lt;1&gt; 版本处理,而不是choice&lt;0&gt; 版本(which it currently calls)。
    • @JustinTime char 是算术运算,因此这与 OP 的排序相匹配。不过很容易翻转。
    • @Barry True。只是想我会指出它,以便他现在可以看到它,而不必稍后在他的项目中搜索潜在的错误。
    【解决方案2】:

    一种可能的方法是添加 is_vector 特征(查看here 了解更多详细信息):

    template<typename T> struct is_vector : public std::false_type {};
    
    template<typename T, typename A>
    struct is_vector<std::vector<T, A>> : public std::true_type {};
    

    然后修改你的convertToString函数模板如下:

    // Arithmetic types
    
    template<class T>
    typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type convertToString(const T& t) {
        return std::to_string(t);
    }
    
    // Other types using string ctor
    
    template<class T>
    typename std::enable_if<!std::is_arithmetic<T>::value && !is_vector<T>::value, std::string>::type convertToString(const T& t) {
        return std::string(t);
    }
    
    // Vectors
    
    template<class T>
    typename std::enable_if<!std::is_arithmetic<T>::value && is_vector<T>::value, std::string>::type convertToString(const T& t) {
        std::string str;
        for(std::size_t i = 0; i < t.size(); i++){
            str += convertToString(t[i]);
        }
        return str;
    }
    

    wandbox example

    【讨论】:

      【解决方案3】:

      标有错误的模板:

      template<class T>
      typename std::enable_if<std::__and_<std::__not_<std::is_arithmetic<T>>::type,
      //                                                                    ^^^^^^[1]
              std::__not_<std::is_same<T, <T,
      //                                  ^^^[2]
             std::vector<typename T::value_type, typename T::allocator_type>>::value
      //                                                                     ^^^^^^^[3]
             >>>::value, std::string>::type convertToString(const T& t){
      //       ^[4]
          return std::string(t);
      }
      // [1] nested ::type not needed and ill-formed without typename keyword
      // [2] <T, is garbage
      // [3] nested ::value ill-formed because std::__not_ argument must be a type
      // [4] too many closing angle brackets
      

      已修复错误的模板:

      template<class T>
      typename std::enable_if<std::__and_<std::__not_<std::is_arithmetic<T>>,
              std::__not_<std::is_same<T,
             std::vector<typename T::value_type, typename T::allocator_type>>
             >>::value, std::string>::type convertToString(const T& t){
          return std::string(t);
      }
      

      【讨论】:

      • 我们也不要使用特定标准库供应商的实现细节。
      • 我知道,我不确定是否可以使用 && 或 !因为标准库不使用它。
      • @T.C.超出了问题的范围(无论如何我都投票结束了),但请随时将其编辑到此答案中或提供您自己的答案。
      • @Overblade 标准库实现的源代码是一个可怕学习C++的地方。
      • 哈哈,我知道了:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-11
      • 2016-12-19
      • 2021-12-03
      相关资源
      最近更新 更多