如何检测类中是否存在特定的成员变量?


73

为了创建算法模板函数,我需要知道在作为模板参数的类中是x还是X(以及y或Y)。当将我的函数用于MFC CPoint类或GDI + PointF类或其他一些函数时,此方法可能很有用。他们都在其中使用不同的x。我的解决方案可以简化为以下代码:


template<int> struct TT {typedef int type;};
template<class P> bool Check_x(P p, typename TT<sizeof(&P::x)>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<sizeof(&P::X)>::type b = 0) { return false; }

struct P1 {int x; };
struct P2 {float X; };
// it also could be struct P3 {unknown_type X; };

int main()
{
    P1 p1 = {1};
    P2 p2 = {1};

    Check_x(p1); // must return true
    Check_x(p2); // must return false

    return 0;
}

但是它不能在Visual Studio中编译,而可以在GNU C ++中进行编译。通过Visual Studio,我可以使用以下模板:


template<class P> bool Check_x(P p, typename TT<&P::x==&P::x>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<&P::X==&P::X>::type b = 0) { return false; }

但是它不能在GNU C ++中编译。有通用解决方案吗?

UPD:这里的结构P1和P2仅作为示例。可能有任何具有未知成员的类。

PS请不要在此处发布C ++ 11解决方案,因为它们很明显并且与问题无关。


我不相信第二种方法是标准的(积分常量表达式不能对涉及op&的操作数使用op ==)。但是第一种方法看起来正确。msvc ++对此有何评论?
Johannes Schaub-litb

@litb:看看我答案的结尾处的链接-我认为这可以解释问题(这是为什么编译器会拒绝它,以及C ++ 98标准是否真正允许它)。
James Hopkin

1
+1:有趣的挑战:-)
詹姆斯·霍普金

我已经为解决此问题编写了正确代码的详尽解释,可在此处找到它:cpptalk.wordpress.com/2009/09/12/…。很抱歉两次发表此评论,但我认为它属于主要帖子。
rmn

Answers:


50

另一种方式是这种方式,它也依赖于SFINAE来表达。如果名称查找导致歧义,编译器将拒绝该模板

template<typename T> struct HasX { 
    struct Fallback { int x; }; // introduce member name "x"
    struct Derived : T, Fallback { };

    template<typename C, C> struct ChT; 

    template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1]; 
    template<typename C> static char (&f(...))[2]; 

    static bool const value = sizeof(f<Derived>(0)) == 2;
}; 

struct A { int x; };
struct B { int X; };

int main() { 
    std::cout << HasX<A>::value << std::endl; // 1
    std::cout << HasX<B>::value << std::endl; // 0
}

它基于Usenet上某人的出色创意。

注意:HasX检查任意类型的任何称为x的数据或函数成员。引入成员名称的唯一目的是使成员名称查找可能不明确-成员的类型并不重要。


起初我不明白这个主意。那正是我所需要的。此解决方案在MSVC ++ 2008和g ++ 4.2.4中均适用。
Kirill V. Lyadvinsky 09年

3
是的,屏住呼吸:groups.google.com/group/comp.lang.c ++。moderated / tree / browse_frm /…(也由一个人从您链接的SO线程链接)。如果您不立即了解该人的代码所提供的功能,请不用担心。这非常聪明,也花了我一段时间
Johannes Schaub-litb

1
我在博客上添加了与此相关的帖子,希望大家不要介意:) cpptalk.wordpress.com/2009/09/11/…有趣的阅​​读。
rmn

1
@rmn,它并不能检查的integer部件,虽然,它检查调用的任何数据或功能部件x,具有任意类型。引入成员名称的唯一目的是使成员名称查找可能不明确-成员的类型并不重要。
Johannes Schaub-litb

2
现在,此代码的完整说明已发布..在:cpptalk.wordpress.com/2009/09/12/…litb ,非常欢迎您验证我是否正确:)
rmn

108

这里是一个解决方案不是简单的 约翰内斯·绍布- litb一个。它需要C ++ 11。

#include <type_traits>

template <typename T, typename = int>
struct HasX : std::false_type { };

template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

更新:一个简单的示例及其解释。

对于这些类型:

struct A { int x; };
struct B { int y; };

我们有HasX<A>::value == trueHasX<B>::value == false。让我们看看为什么。

首先回想一下,std::false_typestd::true_type有一个static constexpr bool名为的成员,分别value设置为falsetrue。因此,HasX上面的两个模板继承了该成员。(来自std::false_type的第一个模板和来自的第二个模板std::true_type。)

让我们开始简单,然后逐步进行,直到获得上面的代码。

1)起点:

template <typename T, typename U>
struct HasX : std::false_type { };

在这种情况下,毫不奇怪:HasXstd::false_type和派生于HasX<bool, double>::value == falseHasX<bool, int>::value == false

2)默认值U

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

鉴于 U默认为intHas<bool>实际上意味着HasX<bool, int>,因此HasX<bool>::value == HasX<bool, int>::value == false

3)添加专业化:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX<T, int> : std::true_type { };

通常,由于有了主模板,因此HasX<T, U>派生自std::false_type。但是,对于U = int从衍生std::true_type。因此,HasX<bool, double>::value == false但是HasX<bool, int>::value == true

多亏了默认设置UHasX<bool>::value == HasX<bool, int>::value == true

4) decltype和一种奇特的说法int

这里有点题外话,但是请忍受我。

基本上(这并不完全正确)会decltype(expression)产生expression的类型 。例如,因此0具有类型intdecltype(0)意味着int。类似地,1.2具有类型double,因此,decltype(1.2)意味着double

考虑具有以下声明的函数:

char func(foo, int);

什么foo是类类型。如果f是类型的对象foo,则decltype(func(f, 0))表示char(由返回的类型func(f, 0))。

现在,该表达式(1.2, 0)使用(内置)逗号运算符,该运算符按顺序计算两个子表达式(即,首先是,1.2然后是0),丢弃第一个值,并得出第二个值。因此,

int x = (1.2, 0);

相当于

int x = 0;

将其与decltype给出的decltype(1.2, 0)意思是int。这里1.2double这里没有什么特别的。例如,true具有类型booldecltype(true, 0)手段int

那类类型呢?对于instace,这decltype(f, 0)意味着什么?很自然地希望这仍然意味着,int但事实并非如此。确实,逗号运算符可能与func上面的函数类似,需要afoo和anint并返回a char。在这种情况下,decltype(foo, 0)char

我们如何避免对逗号运算符使用重载?好吧,没有办法为void操作数重载逗号运算符,我们可以将任何内容强制转换为void。因此,decltype((void) f, 0)意味着int。确实,从(void) f强制转换到基本上什么都没做,只是说必须将表达式视为具有type 。然后使用内置的运算符逗号,结果为type 。因此,意味着ffoovoidvoid((void) f, 0)0intdecltype((void) f, 0)int

这个演员真的必要吗?那么,如果有逗号操作符服用无过载fooint那么这是没有必要的。我们总是可以检查源代码,看看是否有这样的运算符。但是,如果它出现在模板中并且f具有V作为模板参数的类型,则不再清楚(甚至无法知道)这种逗号运算符重载是否存在。总而言之,我们还是会强制转换。

底线:decltype((void) f, 0)是一种奇特的说法int

5)SFINAE:

这是一门完整的科学;-)好吧,我很夸张,但这也不是很简单。因此,我将解释保持在最低限度。

SFINAE代表“替换失败不是错误”。这意味着当模板参数被类型替换时,可能会出现非法的C ++代码,但是在某些情况下,编译器并不会中止有问题的代码,就好像没有出现那样,而不是中止编译。让我们看看它如何适用于我们的案例:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

再次,这decltype((void) T::x, 0)是一种奇特的说法int但得益于SFINAE。

T用类型替换时,可能会出现无效的构造。例如,bool::x不是有效的C ++,因此Tboolin替换将T::x产生无效的构造。根据SFINAE原理,编译器不会拒绝代码,而只会忽略(部分)代码。更确切地说,正如我们所看到HasX<bool>的实际含义HasX<bool, int>U = int应该选择的专业化,但是在实例化它时,编译器会发现bool::x并完全忽略模板专业化,就好像它不存在一样。

在这一点上,代码本质上与上面的情况(2)相同,其中仅存在主模板。因此,HasX<bool, int>::value == false

用于相同的论点bool适用于B因为B::x是一个无效的构建体(B没有成员x)。但是,A::x可以,并且编译器在实例化U = int(或更确切地说是U = decltype((void) A::x, 0))的专业化时没有问题。因此,HasX<A>::value == true

6)取消命名U

好了,再次查看(5)中的代码,我们看到该名称U未在任何地方使用,而是在其声明(typename U)中使用。然后,我们可以取消命名第二个模板参数,并获得本文顶部显示的代码。


@Jurak Blaho:问题是GCC 4.7.2。它适用于Clang 3.2和IIRC,也适用于GCC 4.8。(真可惜的是,实时工作空间已经有一段时间没有工作了。)
卡西欧·内里

您能否再解释一下这是如何工作的?
DarioP 2014年

4
@DarioP:我已经添加了说明的更新。如您所见,由于适当的细节量始终取决于读者的知识,因此解释非常冗长。为了初学者的利益,我假设很少。我希望这很清楚,但不要无聊。
卡西欧内里2014年

1
谢谢,如果可以的话,我第二次投票赞成!这太棒了!
DarioP 2014年

2
这是我所见过的最干净,最简单的实现。对我而言,GCC的问题已得到解决,方法是使用默认的模板参数void而不是int,然后删除-ideone.com/QZHfaidecltype
wakjah中

33

我从一个已被关闭的问题(作为该问题的重复部分)的重复此处重定向到这里。我知道这是一个旧线程,但是我只是想建议一个可以与C ++ 11一起使用的替代(更简单?)实现。假设我们要检查某个类是否具有一个名为的成员变量id

#include <type_traits>

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

template<typename T>
struct has_id<T, decltype(std::declval<T>().id, void())> : std::true_type { };

而已。这是它的用法(实时示例):

#include <iostream>

using namespace std;

struct X { int id; };
struct Y { int foo; };

int main()
{
    cout << boolalpha;
    cout << has_id<X>::value << endl;
    cout << has_id<Y>::value << endl;
}

使用几个宏可以使事情变得更加简单:

#define DEFINE_MEMBER_CHECKER(member) \
    template<typename T, typename V = bool> \
    struct has_ ## member : false_type { }; \
    template<typename T> \
    struct has_ ## member<T, \
        typename enable_if< \
            !is_same<decltype(declval<T>().member), void>::value, \
            bool \
            >::type \
        > : true_type { };

#define HAS_MEMBER(C, member) \
    has_ ## member<C>::value

可以这样使用:

using namespace std;

struct X { int id; };
struct Y { int foo; };

DEFINE_MEMBER_CHECKER(foo)

int main()
{
    cout << boolalpha;
    cout << HAS_MEMBER(X, foo) << endl;
    cout << HAS_MEMBER(Y, foo) << endl;
}

好的解决方案是,如果X具有私有成员变量id,您将遇到问题。
Xoph

1
我认为您根本不需要declval,在我的测试中decltype(T::id, void())效果很好
LB--

7

更新:我最近对原始答案中发布的代码做了更多处理,因此我将对其进行更新以考虑更改/添加。

以下是一些用法摘要:*这一切的胆量都进一步降低了

检查x给定班级的成员。可以是var,func,class,union或enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

检查成员功能void x()

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

检查成员变量x

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

检查会员等级x

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

检查会员工会x

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

检查成员枚举x

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

检查任何成员函数,x无论是否签名:

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

要么

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

详细信息和核心:

/*
    - Multiple inheritance forces ambiguity of member names.
    - SFINAE is used to make aliases to member names.
    - Expression SFINAE is used in just one generic has_member that can accept
      any alias we pass it.
*/

template <typename... Args> struct ambiguate : public Args... {};

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

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(0)) == 1)
        , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
    );

    static bool const value = sizeof(f<Alias>(0)) == 2;
};

巨集(El Diablo!):

CREATE_MEMBER_CHECK:

//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member)                                         \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct Alias_##member;                                                      \
                                                                            \
template<typename T>                                                        \
struct Alias_##member <                                                     \
    T, std::integral_constant<bool, got_type<decltype(&T::member)>::value>  \
> { static const decltype(&T::member) value; };                             \
                                                                            \
struct AmbiguitySeed_##member { char member; };                             \
                                                                            \
template<typename T>                                                        \
struct has_member_##member {                                                \
    static const bool value                                                 \
        = has_member<                                                       \
            Alias_##member<ambiguate<T, AmbiguitySeed_##member>>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_SIG_CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_class_##class_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_class_##class_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_class<                                    \
            typename got_type<typename T::class_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_UNION_CHECK:

//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_union_##union_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_union_##union_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_union<                                    \
            typename got_type<typename T::union_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_ENUM_CHECK:

//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name)                 \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_enum_##enum_name : std::false_type {};    \
                                                            \
template<typename T>                                        \
struct has_member_enum_##enum_name<                         \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_enum<                                     \
            typename got_type<typename T::enum_name>::type  \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::value    \
    ;                                           \
}

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)

可变参数模板在当前的C ++标准中无效。
Kirill V. Lyadvinsky 2011年

需要指出的是,这不适用于继承的方法……但这也许是有意的。我猜std :: integral_constant是这里的限制。
TomSmartBishop 2015年

4

Boost.ConceptTraits之间提供了一些宏来定义类型特征,例如BOOST_TT_EXT_DEFINE_HAS_MEMBER(name),它定义了以下形式的类型特征:

has_member_##name<T>

如果T具有名为的成员类型,则为true。但是请注意,这不会检测引用类型成员。

在这种情况下,添加头文件就足够了

BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x)

并检查如下

BOOST_STATIC_ASSERT(has_member_x<P1>::value);

所使用的技术与上述某些答案中解释的技术相同。

不幸的是,该库不再维护。现在,C ++ 0x将不包含概念,该库与SFINAE一起可以完美替代大多数概念。


1
之所以不再保留概念特征,是因为该功能演变为以下两个特征:boost.org/doc/libs/1_42_0/libs/mpl/doc/refmanual / ...其他功能进入了Concepts_checks,例如:boost.org/doc/ libs / 1_42_0 / libs / concept_check /…
Blaisorblade'5

你是对的。has_xxx已经在Boost中并回答问题。我不同意第二个链接,因为原型和概念检查是同一枚硬币的两个方面。
Vicente Botet Escriba,2010年

2

为什么不使用这样的专业化:

struct P1 {int x; };
struct P2 {int X; };

template<class P> 
bool Check_x(P p) { return true; }

template<> 
bool Check_x<P2>(P2 p) { return false; }

因为我实际上不知道struct P2包含大X,而P1包含小x。这些结构仅作为示例。可以有任何结构或类。
Kirill V. Lyadvinsky 09年

然后,我看不到使用模板重新确认的任何方法(可能是我错了)。如果x的数据类型在P1和P2中不同,则可能是我们使用sizeof返回了true或false。
Naveen

在我的问题中,有办法(实际上有两种不同的办法)。但是我不知道如何在两个编译器中识别它。
Kirill V. Lyadvinsky 09年

2

我们可以使用C ++ 20 require表达式来解决此问题。向@lefticus致谢,他最近在C ++ Weekly-Ep 242-C ++ 20中的内省设计(概念+ if constexpr

#include <iostream>

struct P1 {int x;};
struct P2 {float X;};

bool has_x(const auto &obj) {
    if constexpr (requires {obj.x;}) {
      return true;
    } else
      return false;
}

int main()
{
    P1 p1 = {1};
    P2 p2 = {1};

    std::cout << std::boolalpha << has_x(p1) << "\n"; 
    std::cout << has_x(p2) << "\n"; 

    return 0;
}

你可以看到 在这里


1

您为什么不只创建Check_x的模板专业化?

template<> bool Check_x(P1 p) { return true; }
template<> bool Check_x(P2 p) { return false; }

哎呀,当我想到它。如果只有两种类型,为什么还要为此使用模板?


不仅有两种类型。看看我对Naveen答案的评论。
Kirill V. Lyadvinsky 09年

1

函数(x,X,y,Y)是来自抽象基类,还是可以重构为这样?如果是这样,则可以使用Modern C ++ Design中的SUPERSUBCLASS()宏以及该问题的答案的思想:

基于编译时类型的调度


0

我们可以在编译时获取:0 - not_member, 1 - is_object, 2 - is_function对于每个必需的类和成员-对象或函数:http : //ideone.com/Fjm9u5

#include <iostream>
#include <type_traits>

#define IS_MEMBER(T1, M)    \
struct {        \
    struct verystrangename1 { bool M; };    \
    template<typename T> struct verystrangename2 : verystrangename1, public T { }; \
    \
    enum return_t { not_member, is_object, is_function }; \
    template<typename T, typename = decltype(verystrangename2<T>::M)> constexpr return_t what_member() { return not_member;  }  \
    template<typename T> typename std::enable_if<std::is_member_object_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_object; }   \
    template<typename T> typename std::enable_if<std::is_member_function_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_function; }   \
    constexpr operator return_t() { return what_member<T1>(); } \
}

struct t {
    int aaa;
    float bbb;
    void func() {}
};

// Can't be in function
IS_MEMBER(t, aaa) is_aaa_member_of_t;
IS_MEMBER(t, ccc) is_ccc_member_of_t;
IS_MEMBER(t, func) is_func_member_of_t;

// known at compile time
enum { const_is_aaa_member_of_t = (int)is_aaa_member_of_t };
static constexpr int const_is_func_member_of_t = is_func_member_of_t;

int main() {        
    std::cout << std::boolalpha << "0 - not_member, 1 - is_object, 2 - is_function \n\n" <<
        "is aaa member of t = " << is_aaa_member_of_t << std::endl << 
        "is ccc member of t = " << is_ccc_member_of_t << std::endl << 
        "is func member of t = " << is_func_member_of_t << std::endl << 
        std::endl;

    return 0;
}

结果:

0 - not_member, 1 - is_object, 2 - is_function 

is aaa member of t = 1
is ccc member of t = 0
is func member of t = 2

对于类/结构:

struct t {
    int aaa;
    float bbb;
    void func() {}
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.