【问题标题】:Check if member is declared in class检查成员是否在类中声明
【发布时间】:2018-01-25 10:48:48
【问题描述】:

是否可以检查给定类中是否声明了成员变量、成员函数或类型定义?

StackOverflow 上的各种问题都谈到了检查给定类是否仅包含一个成员,主要是使用 std::is_detected。但是所有这些解决方案也在派生类中检测到成员,派生类可能不会自己声明成员。

例如,以下内容无法编译。

#include <experimental/type_traits>

struct base
{
  using type = std::true_type;
};

struct derived : public base { };

template<typename T>
using has_type_t = typename T::type;

template<typename T>
constexpr inline bool has_type_v =         
std::experimental::is_detected<has_type_t, T>::value;

int main ()
{
  static_assert (has_type_v<base>);
  static_assert (!has_type_v<derived>);
}

可以进行任何更改以使这两个断言成立吗?还是需要反思?

【问题讨论】:

  • struct derived : public base { using base::type; }; 的预期结果是什么?
  • 你的用途是什么?您可以检查成员 Btw,但类型...
  • @VTT 对于这种情况,我希望得到“真”。所以我希望它在示例中表现为“基础”,因为它声明了自己的成员。但我想,在这方面不同的解决方案也会很有趣/正确。
  • @Jarod42 我想将某些类标记为允许用作另一个模板的模板参数。我目前正在为此使用类型特征,但是用户必须编写一些样板文件才能选择加入。如果他们能说“使用 is_allowed_for_use = std::true_type;”会更好或类似于选择加入。因此,检查静态成员对我来说同样有用。
  • 我认为您所描述的内容需要某种形式的反射,除非我们能找到一些惯用的方法在每个派生类中正确隐藏type(但即使那样它也不会成为“即插即用”的解决方案)

标签: c++ sfinae typetraits c++17


【解决方案1】:

我没有看到类型或静态成员的方法,但对于普通成员,您可以区分 base::mderived::m

template<typename T>
using has_m_t = decltype(&T::m);

template<typename T>
constexpr inline bool has_m_v =
    std::experimental::is_detected_exact<int (T::*), has_m_t, T>::value;

还有测试:

struct base { int m; };
struct derived : public base {};
struct without {};

static_assert( has_m_v<base>);
static_assert(!has_m_v<derived>);
static_assert(!has_m_v<without>);

Demo

【讨论】:

    【解决方案2】:

    我倾向于拒绝。证明这一点当然很难,但我可以解释我为什么这么认为。

    C++ 是一种编译语言。编译器具有类型的内部表示,您无法直接访问。访问这种内部表示的唯一方法是通过语言的工具。实际实现在内部表示类型的方式可能会有所不同,并且通常确实有额外的信息来产生更好的错误消息。但这并没有暴露出来。

    所以是的,大多数编译器都可以枚举基本类型,并且准确地知道类的每个成员来自哪里。这对于良好的错误消息至关重要。但是你不能在编译时枚举基类,只使用 C++ 模板。

    您可能认为对于数据成员,您可以尝试使用地址和偏移量的技巧。这也行不通,因为您需要知道所有基类的大小 - 而您无法枚举它们。

    您还可以考虑创建进一步派生的辅助类的技巧。这是可能的,但他们面临同样的问题。您只能从给定类型派生,而不能从其父类型派生。因此可以创建一个子类,但不能创建一个兄弟。那个孩子从父母和祖父母那里继承。即使你在助手类中写了using derived::type,也会找到base::type

    【讨论】:

    • 会员可以知道:Demo
    • @Jarod42:看起来这是正确的答案,并且(总结)您的技巧是&amp;Derived::member 在继承时具有某种类型Base::*。我可以建议你把它变成一个答案吗?
    • 在类中添加一个虚拟成员似乎不是解决 OP 问题的干净方法。 type 或静态成员会做(但我看不到检查的方法)。
    • 我同意 @Jarod42 的回答对原始问题最具建设性(因为该问题还询问成员变量),即使它并没有真正解决我的问题。因此应该选择它作为解决方案。除此之外,我认为这里的这个答案可能是最准确的,虽然它的结论有点悲哀。
    【解决方案3】:

    您可以但有一个限制,您的编译器必须具有提供基类列表的内在函数(GCC 提供它)。

    如果您可以访问此类 en 内在,唯一的困难是检查通过派生的成员访问实际上不是对基成员的访问。

    要检查这一点,我们的想法是使用在访问派生类的多个基中声明的成员时发生的“模糊”访问:

    struct base
    {
      using type = std::true_type;
    };
    
    struct Derived1 : base{
    
    };
    struct Derived2 : base{
     using type = std::true_type;
    };
    
    struct Test1: Derived1,base{};
    struct Test2: Derived2,base{};
    
    void g(){
        Test1::type a;
        Test2::type b;//Do not compile: 'type' is ambiguous
    }
    

    所以你可以这样概括这个技巧:

    template<class T,class Base>
    struct MultiDer: T,Base{};
    
    template<class T,class Base,class=void>
    struct has_ambiguous_type
      :std::true_type{};
    
    template<class T,class Base>
    struct has_ambiguous_type
            <T,Base,std::void_t<typename MultiDer<T,Base>::type>>
      :std::false_type{};
    
    template<class T,class=void>
    struct has_type
      :std::false_type{};
    
    template<class T>
    struct has_type
            <T,std::void_t<typename T::type>>
      :std::true_type{};
    
    template<class T,class...Bases>
    constexpr inline auto has_type_declared_imp = 
         has_type<T>::value
      //if any ambiguous access happens then T has 'type'
      && (   (has_ambiguous_type<T,Bases>::value || ...) 
      //or no ambiguity happened because none of the base has 'type'
          || (!has_type<Bases>::value && ...)); 
    
    template<class T>
    constexpr inline auto has_type_declared = 
      has_type_declared_imp<T,__direct_bases(T)...>;//GCC instrinsic
    
    static_assert(has_type_declared<Derived2>);
    static_assert(!has_type_declared<Derived1>);
    

    唯一的问题是可移植性:您的编译器必须提供一种机制来访问类型的直接基类列表。这里我使用了 GCC 的内在 __direct_bases

    【讨论】:

    • 有趣的想法!但我一直在寻找符合标准的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-16
    • 1970-01-01
    相关资源
    最近更新 更多