【问题标题】:check if member exists using enable_if使用 enable_if 检查成员是否存在
【发布时间】:2012-11-27 00:52:17
【问题描述】:

这是我想要做的:

template <typename T> struct Model
{
    vector<T> vertices ;

    #if T has a .normal member
    void transform( Matrix m )
    {
        each vertex in vertices
        {
          vertex.pos = m * vertex.pos ;
          vertex.normal = m * vertex.normal ;
        }
    }
    #endif

    #if T has NO .normal member
    void transform( Matrix m )
    {
        each vertex in vertices
        {
          vertex.pos = m * vertex.pos ;
        }
    }
    #endif
} ;

我见过examples 使用enable_if,但我不明白如何将enable_if 应用于这个问题,或者它是否可以应用。

【问题讨论】:

  • enable_if 不用于检查成员是否存在,而是用于删除重载。
  • 我不能用它来做类似的事情(上面编辑中的建议)吗?
  • 不,您想要一个尚不存在的static if。你想要的完全有可能,只是不会使用那样的语法。

标签: c++ templates sfinae


【解决方案1】:

这在 C++11 中变得方式更容易了。

template <typename T> struct Model
{
    vector<T> vertices;

    void transform( Matrix m )
    {
        for(auto &&vertex : vertices)
        {
          vertex.pos = m * vertex.pos;
          modifyNormal(vertex, m, special_());
        }
    }

private:

    struct general_ {};
    struct special_ : general_ {};
    template<typename> struct int_ { typedef int type; };

    template<typename Lhs, typename Rhs,
             typename int_<decltype(Lhs::normal)>::type = 0>
    void modifyNormal(Lhs &&lhs, Rhs &&rhs, special_) {
       lhs.normal = rhs * lhs.normal;
    }

    template<typename Lhs, typename Rhs>
    void modifyNormal(Lhs &&lhs, Rhs &&rhs, general_) {
       // do nothing
    }
};

注意事项:

  • 您可以在decltypesizeof 中命名非静态数据成员,而无需对象。
  • 您可以应用扩展的 SFINAE。基本上可以检查任何表达式,如果在替换参数时它无效,则忽略模板。

【讨论】:

  • 这绝对比 before 更好的 SFINAE,您必须为每个要检测的成员类型创建一个成员检测器。但是为什么人们总是跳到SFINAE,我现在更喜欢用type traits来做这种条件编译。
  • 我想这取决于您是否有更多类型要检查或更多成员要检查?
  • 类型特征只有在你提前知道类型时才有帮助。在您的链接中,您必须知道 VertexN 是特殊类。因此它们可能在 OP 的示例中有效,但即使您不知道其他类型将是什么,SFINAE 也有效,IMO 是更好的封装。
  • 我喜欢使用general_和special_,我发现它比使用int和long更清晰。
【解决方案2】:

我知道这个问题已经有了一些答案,但我认为我对这个问题的解决方案有点不同,可以帮助某人。

以下示例检查传递的类型是否包含c_str() 函数成员:

template <typename, typename = void>
struct has_c_str : false_type {};

template <typename T>
struct has_c_str<T, void_t<decltype(&T::c_str)>> : std::is_same<char const*, decltype(declval<T>().c_str())>
{};

template <typename StringType,
          typename std::enable_if<has_c_str<StringType>::value, StringType>::type* = nullptr>
bool setByString(StringType const& value) {
    // use value.c_str()
}

如果需要检查传递的类型是否包含特定的数据成员,可以使用以下方法:

template <typename, typename = void>
struct has_field : std::false_type {};

template <typename T>
struct has_field<T, std::void_t<decltype(T::field)>> : std::is_convertible<decltype(T::field), long>
{};

template <typename T,
          typename std::enable_if<has_field<T>::value, T>::type* = nullptr>
void fun(T const& value) {
    // use value.field ...
}

更新 C++20

C++20 在这个 C++ 版本中引入了约束和概念、核心语言特性。

如果我们要检查模板参数是否包含c_str成员函数,那么,下面的工作就完成了:

template<typename T>
concept HasCStr = requires(T t) { t.c_str(); };

template <HasCStr StringType> 
void setByString(StringType const& value) {
    // use value.c_str()
}

此外,如果我们要检查可转换为long的数据成员是否存在,可以使用以下方法:

template<typename T>
concept HasField = requires(T t) {
    { t.field } -> std::convertible_to<long>;
};

template <HasField T> 
void fun(T const& value) {
    // use value.field
}

通过使用 C++20,我们可以得到更短、更易读的代码,清楚地表达它的功能。

【讨论】:

  • C++20 版本是迄今为止最好和最易读的解决方案。我已经(ab)使用它来有效地将成员函数的默认实现事后添加到现有类型库中,而无需修改它们以从基类继承默认实现:template &lt;class T&gt; auto call_member(T t) { if constexpr(HasMember&lt;T&gt;) { return t.member(); } else { return default_action(); } }。 (概念要求只是一个constexpr 布尔表达式,所以它可以像这样在if constexpr 中使用就好了)
【解决方案3】:

您需要一个元函数来检测您的成员,以便您可以使用enable_if。这样做的成语称为Member Detector。这有点棘手,但可以做到!

【讨论】:

    【解决方案4】:

    这不是针对您的确切情况的答案,而是对问题标题和一般问题的替代答案。

    #include <iostream>
    #include <vector>
    
    struct Foo {
        size_t length() { return 5; }
    };
    
    struct Bar {
        void length();
    };
    
    template <typename R, bool result = std::is_same<decltype(((R*)nullptr)->length()), size_t>::value>
    constexpr bool hasLengthHelper(int) { 
        return result;
    }
    
    template <typename R>
    constexpr bool hasLengthHelper(...) { return false; }
    
    template <typename R>
    constexpr bool hasLength() {
        return hasLengthHelper<R>(0);
    }
    
    // function is only valid if `.length()` is present, with return type `size_t`
    template <typename R>
    typename std::enable_if<hasLength<R>(), size_t>::type lengthOf (R r) {
      return r.length();
    }
    
    int main() {
        std::cout << 
          hasLength<Foo>() << "; " <<
          hasLength<std::vector<int>>() << "; " <<
          hasLength<Bar>() << ";" <<
          lengthOf(Foo()) <<
          std::endl;
        // 1; 0; 0; 5
    
        return 0;
    }
    

    相关https://ideone.com/utZqjk

    感谢 dyreshark 在 freenode IRC #c++

    【讨论】:

      【解决方案5】:
      template<
      typename HTYPE, 
      typename = std::enable_if_t<std::is_same<decltype(HTYPE::var1), decltype(HTYPE::var1)>::value>
      >
      static void close_release
      (HTYPE* ptr) {
          ptr->var1;
      }
      

      使用 enable_if 和 decltype 让编译器检查变量,希望有所帮助。

      【讨论】:

      • std::is_same&lt;decltype(HTYPE::var1), decltype(HTYPE::var1)&gt;::value 可以更简单地写成std::is_pointer&lt;decltype(&amp;HTYPE::var1)&gt;::value。另外,添加&amp; 可以避免在检查函数是否存在时出现编译器错误。
      【解决方案6】:

      我知道现在有点晚了,但是......

      typedef int Matrix;
      
      struct NormalVertex {
          int pos;
          int normal;
      };
      
      struct Vertex {
          int pos;
      };
      
      template <typename T> struct Model
      {
          typedef int No;
          typedef char Yes;
      
          template<typename U> static decltype (declval<U>().normal, Yes()) has_normal(U a);
          static No has_normal(...);
      
          vector<T> vertices ;
      
          template <typename U = T>
          typename enable_if<sizeof(has_normal(declval<U>())) == sizeof(Yes), void>::type
          transform( Matrix m )
          {
              std::cout << "has .normal" << std::endl;
              for (auto vertex : vertices)
              {
                vertex.pos = m * vertex.pos ;
                vertex.normal = m * vertex.normal ;
              }
          }
      
          template <typename U = T>
          typename enable_if<sizeof(has_normal(declval<U>())) == sizeof(No), void>::type
          transform( Matrix m )
          {
              std::cout << "has no .normal" << std::endl;
              for (auto vertex : vertices)
              {
                vertex.pos = m * vertex.pos ;
              }
          }
      } ;
      
      int main()
      {
          Matrix matrix;
          Model <NormalVertex> normal_model;
      
          Vertex simple_vertex;
          Model <Vertex> simple_model;
      
          simple_model.transform(matrix);
          normal_model.transform(matrix);
      
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多