使用enable_if检查成员是否存在

问题描述 投票:0回答:8

这就是我正在尝试做的事情:

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
} ;

我看过使用enable_if

示例
,但我不明白如何将
enable_if
应用于这个问题,或者是否可以应用。

c++ templates sfinae
8个回答
35
投票

使用 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
    }
};

注意事项:

  • 上面检查成员字段,检查方法将“
    decltype(Lhs::normal)
    ”部分更改为“
    decltype(&Lhs::myFunc)
    ”。
  • 您可以在
    decltype
    sizeof
    中命名非静态数据成员,而不需要对象。
  • 您可以申请延长SFINAE。基本上任何表达式都可以被检查,如果在替换参数时它无效,则模板将被忽略。

11
投票

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

以下示例检查传递的类型是否包含

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,我们可以获得更短、更易读的代码,清楚地表达其功能。


8
投票

您需要一个元函数来检测您的会员,以便您可以使用

enable_if
。执行此操作的习惯称为“成员检测器”。虽然有点棘手,但是可以做到!


3
投票

#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

.

归功于 freenode IRC 上的 dyreshark #c++


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


3
投票
requires

关键字,但提供的代码对于您的需求来说仍然太复杂,需要为每种情况创建单独的函数。这是适合您的用例的更简单的代码,其中单个函数实现就足够了:

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

   void transform( Matrix m )
   {
      each vertex in vertices
      {
         vertex.pos = m * vertex.pos ;
         if constexpr (requires { &vertex.normal; })
            vertex.normal = m * vertex.normal ;
      }
   }
} ;

备注:

    所有的技巧都在
  • if constexpr

    线上。我按原样保留了伪代码,但删除了冗余并添加了

    if constexpr
    行。
    
    

  • 我添加的
  • requires

    表达式只是尝试访问

    normal
    成员的地址,如果表达式无效,则计算结果为
    false
    。您确实可以使用任何表达式,如果定义了
    normal
    ,则该表达式会成功;如果未定义,则失败。
    
    

  • 对于确实具有
  • normal

    的类,请确保可以从此代码访问该成员(例如,它是

    public
    或指定了适当的友谊)。否则,代码将忽略
    normal
    成员,就好像它根本不存在一样。
    
    

  • 有关更多信息,请参阅
  • https://en.cppreference.com/w/cpp/language/constraints

    上的“简单要求”部分。


1
投票

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; }



1
投票

#include <boost/tti/has_member_data.hpp> BOOST_TTI_HAS_MEMBER_DATA(normal) template <typename T> struct Model { vector<T> vertices; static constexpr bool hasNormal = has_member_data_normal<T, double>::value; template<bool B = hasNormal, std::enable_if_t<B, int> = 0> void transform( Matrix m ) { for(auto&& vertex : vertices) { vertex.pos = m * vertex.pos ; vertex.normal = m * vertex.normal ; } } template<bool B = hasNormal, std::enable_if_t<!B, int> = 0> void transform( Matrix m ) { for(auto&& vertex : vertices) { vertex.pos = m * vertex.pos ; } } };

如果你不想依赖boost,那么你可以使用
@ltjax的答案

来创建你自己的has_member_data_normal结构。

    

© www.soinside.com 2019 - 2024. All rights reserved.