C++ 预处理器测试 class 成员是否存在
C++ preprocessor test if class member exists
是否有等效的 #ifdef 来测试成员是否存在于 class 中,以便可以在不导致代码编译器失败的情况下完成处理。我尝试过模板操作,但特定问题没有成功。
例如
#if member baseclass.memberA()
baseclass.memberA().push_back(data);
#else
doAlternate(data);
#endif
显然以上内容无效,但我正在尝试发现是否已将类似的内容添加到 C++11
请注意,在初始设置中,将存在 memberA、memberB、memberC...,每个都需要 push_back。其他成员将来会添加到baseclass中,这就是为什么我想创建一个模板,以便即使当前baseclass没有一些,所有案例也能正确编译和处理成员(例如 memberX)。否则,我可以用一个非常简单的模板放入 push_back() 行。
这其实是最简单的情况。还有一种情况,我创建了 subclass 的实例化,然后将其推回 subclass 成员。
// Instantiate an element of the Maindata class
::basedata::Maindata maindata;
//Instantiate an element of the Subdata class
::basedata::Subdata subinfo("This goes into the subinfo vector");
// Process some data that is part of the Subdata class
subinfo.contexts(contextInfo);
// Push the instantiated Subdata into the Subdata member of Maindata
maindata.subdata().push_back(subinfo);
请注意,需要设置 Subdata 和 subdata() 以便实现适当的代码。但是,如果 ::basedata::Subdata 存在,那么 maindata.subdata().
也会存在
我已经尝试过各种使用模板的方法,但收到的各种答案都无法解决特定问题。例如 , , C++ template for variable type declaration
不适用于预处理器,但以下内容可能会有所帮助:
#include <cstdint>
#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature) \
template <typename U> \
class traitsName \
{ \
private: \
template<typename T, T> struct helper; \
template<typename T> \
static std::uint8_t check(helper<signature, &funcName>*); \
template<typename T> static std::uint16_t check(...); \
public: \
static \
constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
}
DEFINE_HAS_SIGNATURE(has_memberA, T::memberA, std::vector<int> (T::*)(void));
然后,使用 SFINAE:
#include <type_traits>
template <typename T>
std::enable_if<has_memberA<T>::value>::type
doBase(T& t, int data)
{
t.memberA().push_back(data);
}
template <typename T>
std::enable_if<!has_memberA<T>::value>::type
doBase(T& , int data)
{
doAlternate(data);
}
这只是 void_t
的另一种情况。
我们需要一个小助手模板 Void
并定义一个方便的模板类型别名 void_t
。
#include <type_traits>
template<typename...>
struct Void { using type = void; };
template<typename... T>
using void_t = typename Void<T...>::type;
我们定义了实现回退策略的主要模板。
template<typename T, typename = void>
struct Helper
{
static void
function(T& t)
{
std::cout << "doing something else with " << &t << std::endl;
}
};
并为支持特定操作的类型提供部分特化,在本例中,.data().push_back(int)
。
template<typename T>
struct Helper<T, void_t<decltype(std::declval<T>().data().push_back(0))>>
{
static void
function(T& t)
{
std::cout << "pushing back data to " << &t << std::endl;
t.data().push_back(42);
}
};
为了向我们的客户隐藏 Helper
实现细节并允许模板参数的类型推导,我们可以很好地包装它。
template<typename T>
void
function(T& t)
{
Helper<T>::function(t);
}
这就是我们的客户使用它的方式。
#include <iostream>
#include <vector>
class Alpha
{
public:
std::vector<int>& data() { return this->data_; }
private:
std::vector<int> data_ {};
};
class Beta { /* has no data() */ };
int
main()
{
Alpha alpha {};
Beta beta {};
std::cout << "&alpha = " << &alpha << std::endl;
std::cout << "&beta = " << &beta << std::endl;
function(alpha);
function(beta);
}
可能的输出:
&alpha = 0x7ffffd2a3eb0
&beta = 0x7ffffd2a3eaf
pushing back data to 0x7ffffd2a3eb0
doing something else with 0x7ffffd2a3eaf
更新:如何将此技术应用于多个成员
上面显示的技术可以应用于任意数量的成员。让我们举一个小例子。假设我们要编写一个模板函数 frobnicate
,它接受一个泛型类型的参数,如果对象有……
- …一个不带参数的成员函数
incr
,调用它,
- …一个数据成员
name
,如果可能的话向其附加一些文本并且
- …一个数据成员
numbers
,push_back
如果可能,给它一些数字。
我真的建议您通过实施三个助手struct
来解决这个问题,如上所示。它没有那么多多余的输入,并使代码更清晰。
但是,如果您想忽略此建议,让我们看看如何使用宏来减少输入。假设和上图一样定义void_t
,我们可以定义如下宏
#define MAKE_SFINAE_HELPER(NAME, TYPE, OPERATION, ARGS, CODE) \
template<typename TYPE, typename = void> \
struct NAME \
{ \
template<typename... AnyT> \
void \
operator()(AnyT&&...) noexcept \
{ \
/* do nothing */ \
} \
}; \
\
template<typename TYPE> \
struct NAME<TYPE, void_t<decltype(std::declval<TypeT>()OPERATION)>> \
{ \
void operator()ARGS noexcept(noexcept(CODE)) \
{ \
CODE; \
} \
};
它将在类型参数 TYPE
上定义一个名为 NAME
的 struct
模板,并定义一个带有运算符 ()
的主模板,该模板接受任意数量的参数任何类型,什么都不做。如果不支持所需的操作,这将用作回退。
但是,如果类型 TYPE
的对象支持操作 OPERATION
,则带有运算符 ()
的偏特化采用参数 ARGS
并执行 CODE
将被使用。宏定义为 ARGS
可以是带括号的参数列表。不幸的是,预处理器语法只允许将单个表达式作为 CODE
传递。这不是一个大问题,因为我们总是可以编写一个委托给另一个函数的函数调用。 (请记住,计算机科学中的任何问题都可以通过添加额外的间接层来解决——当然,间接层太多的问题除外……)偏特化的运算符 ()
将被声明为 noexcept
当且仅当 CODE
是。 (这也只适用于 CODE
仅限于单个表达式。)
主模板的运算符 ()
是模板的原因是,否则编译器可能会发出有关未使用变量的警告。当然,您可以更改宏以接受附加参数 FALLBACK_CODE
,该参数位于主模板的运算符 ()
的主体中,该运算符应使用相同的 ARGS
。
在最简单的情况下,可以将 OPERATION
和 CODE
参数合并为一个参数,但 CODE
不能有效地引用 ARGS
将 ARGS
限制为 TYPE
类型的单个参数,在这种情况下,如果您不需要灵活性,也可以去掉该参数。
所以,让我们将其应用到我们的问题中。首先,我们需要一个辅助函数来推回数字,因为这不能(至少,让我们假装这样)写成单个表达式。我使此函数尽可能通用,仅对成员名称进行假设。
template<typename ObjT, typename NumT>
void
do_with_numbers(ObjT& obj, NumT num1, NumT num2, NumT num3)
{
obj.numbers.push_back(num1);
obj.numbers.push_back(num2);
obj.numbers.push_back(num3);
}
由于其他两个所需的操作可以很容易地写成单个表达式,因此我们不需要对它们进行进一步的间接寻址。所以现在,我们可以生成我们的 SFINAE 助手了。
MAKE_SFINAE_HELPER(HelperIncr,
TypeT,
.incr(),
(TypeT& obj),
obj.incr())
MAKE_SFINAE_HELPER(HelperName,
TypeT,
.name += "",
(TypeT& obj, const std::string& appendix),
obj.name += appendix)
MAKE_SFINAE_HELPER(HelperNumbers,
TypeT,
.numbers.push_back(0),
(TypeT& obj, int i1, int i2, int i3),
do_with_numbers(obj, i1, i2, i3))
有了这些,我们终于可以写出我们的frobnicate
函数了。真的很简单。
template<typename T>
void
frobnicate(T& object)
{
HelperIncr<T>()(object);
HelperName<T>()(object, "def");
HelperNumbers<T>()(object, 4, 5, 6);
}
为了确保一切正常,让我们制作两个 struct
来部分支持相关操作。
#include <string>
#include <vector>
struct Widget
{
std::vector<int> numbers {1, 2, 3};
int counter {};
void incr() noexcept { this->counter += 1; }
};
struct Gadget
{
std::string name {"abc"};
int counter {};
void incr() noexcept { this->counter += 1; }
};
因为我要打印它们,所以我们也定义运算符<<
。
#include <iostream>
std::ostream&
operator<<(std::ostream& os, const Widget& w)
{
os << "Widget : { counter : " << w.counter << ", numbers : [";
int i {};
for (const auto& v : w.numbers)
os << (i++ ? ", " : "") << v;
os << "] }";
return os;
}
std::ostream&
operator<<(std::ostream& os, const Gadget& g)
{
os << "Gadget : { counter : " << g.counter << ", "
<< "name = \"" << g.name << "\" }";
return os;
}
我们开始了:
int
main()
{
Widget widget {};
Gadget gadget {};
std::cout << widget << "\n" << gadget << "\n\n";
frobnicate(widget);
frobnicate(gadget);
std::cout << widget << "\n" << gadget << "\n";
}
输出:
Widget : { counter : 0, numbers : [1, 2, 3] }
Gadget : { counter : 0, name = "abc" }
Widget : { counter : 1, numbers : [1, 2, 3, 4, 5, 6] }
Gadget : { counter : 1, name = "abcdef" }
我鼓励您仔细衡量这种宏观方法的成本和收益。在我看来,额外的复杂性几乎不值得在打字上节省一点钱。
根据这个问题和其他问题的答案,我终于明白,我确实有两个问题需要两个不同的答案。
我有许多 classes 具有不同的成员,所有这些都具有以下类型的操作
::basedata::Maindata maindata;
maindata.subdata().push_back(subinfo);
auto inData maindata.subdata().back();
inData.contexts(contextInfo);
模板的形式是(假设 class subdata() 确实存在(例如问题中的 memberA)。
template<typename T, typename D>
auto doPush(D myData, T & myFormat, contextType contextInfo)
-> decltype(myFormat.push_back(myData), bool())
{
myFormat.push_back(myData);
setcontexts(myFormat.back(), contextInfo)
// Since the push-back() was already done, the new data gets entered
return true;
}
这会调用模板
doPush(dataset, maindata.subdata(), contextInfo);
由于这假设 subdata() 存在,我们现在需要为 subdata 表示的成员设置一个显式测试,并使其成为对通用模板调用的简单包装。
template<typename T, typename D>
auto createMember(D myData, T & myFormat, contextType contextInfo)
-> decltype(myFormat.Member(), bool())
{
dopush(myData, myFormat.Member(), myData);
return true;
}
请注意,如果有足够的位置需要一个,这只需要三个位置来通过宏输入成员名称。
然后实际代码将调用 createMember 模板。
这似乎是最简单的解决方案。
我没有显示虚假案例模板,因为它们很明显。
是否有等效的 #ifdef 来测试成员是否存在于 class 中,以便可以在不导致代码编译器失败的情况下完成处理。我尝试过模板操作,但特定问题没有成功。
例如
#if member baseclass.memberA()
baseclass.memberA().push_back(data);
#else
doAlternate(data);
#endif
显然以上内容无效,但我正在尝试发现是否已将类似的内容添加到 C++11
请注意,在初始设置中,将存在 memberA、memberB、memberC...,每个都需要 push_back。其他成员将来会添加到baseclass中,这就是为什么我想创建一个模板,以便即使当前baseclass没有一些,所有案例也能正确编译和处理成员(例如 memberX)。否则,我可以用一个非常简单的模板放入 push_back() 行。
这其实是最简单的情况。还有一种情况,我创建了 subclass 的实例化,然后将其推回 subclass 成员。
// Instantiate an element of the Maindata class
::basedata::Maindata maindata;
//Instantiate an element of the Subdata class
::basedata::Subdata subinfo("This goes into the subinfo vector");
// Process some data that is part of the Subdata class
subinfo.contexts(contextInfo);
// Push the instantiated Subdata into the Subdata member of Maindata
maindata.subdata().push_back(subinfo);
请注意,需要设置 Subdata 和 subdata() 以便实现适当的代码。但是,如果 ::basedata::Subdata 存在,那么 maindata.subdata().
也会存在我已经尝试过各种使用模板的方法,但收到的各种答案都无法解决特定问题。例如
不适用于预处理器,但以下内容可能会有所帮助:
#include <cstdint>
#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature) \
template <typename U> \
class traitsName \
{ \
private: \
template<typename T, T> struct helper; \
template<typename T> \
static std::uint8_t check(helper<signature, &funcName>*); \
template<typename T> static std::uint16_t check(...); \
public: \
static \
constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
}
DEFINE_HAS_SIGNATURE(has_memberA, T::memberA, std::vector<int> (T::*)(void));
然后,使用 SFINAE:
#include <type_traits>
template <typename T>
std::enable_if<has_memberA<T>::value>::type
doBase(T& t, int data)
{
t.memberA().push_back(data);
}
template <typename T>
std::enable_if<!has_memberA<T>::value>::type
doBase(T& , int data)
{
doAlternate(data);
}
这只是 void_t
的另一种情况。
我们需要一个小助手模板 Void
并定义一个方便的模板类型别名 void_t
。
#include <type_traits>
template<typename...>
struct Void { using type = void; };
template<typename... T>
using void_t = typename Void<T...>::type;
我们定义了实现回退策略的主要模板。
template<typename T, typename = void>
struct Helper
{
static void
function(T& t)
{
std::cout << "doing something else with " << &t << std::endl;
}
};
并为支持特定操作的类型提供部分特化,在本例中,.data().push_back(int)
。
template<typename T>
struct Helper<T, void_t<decltype(std::declval<T>().data().push_back(0))>>
{
static void
function(T& t)
{
std::cout << "pushing back data to " << &t << std::endl;
t.data().push_back(42);
}
};
为了向我们的客户隐藏 Helper
实现细节并允许模板参数的类型推导,我们可以很好地包装它。
template<typename T>
void
function(T& t)
{
Helper<T>::function(t);
}
这就是我们的客户使用它的方式。
#include <iostream>
#include <vector>
class Alpha
{
public:
std::vector<int>& data() { return this->data_; }
private:
std::vector<int> data_ {};
};
class Beta { /* has no data() */ };
int
main()
{
Alpha alpha {};
Beta beta {};
std::cout << "&alpha = " << &alpha << std::endl;
std::cout << "&beta = " << &beta << std::endl;
function(alpha);
function(beta);
}
可能的输出:
&alpha = 0x7ffffd2a3eb0
&beta = 0x7ffffd2a3eaf
pushing back data to 0x7ffffd2a3eb0
doing something else with 0x7ffffd2a3eaf
更新:如何将此技术应用于多个成员
上面显示的技术可以应用于任意数量的成员。让我们举一个小例子。假设我们要编写一个模板函数 frobnicate
,它接受一个泛型类型的参数,如果对象有……
- …一个不带参数的成员函数
incr
,调用它, - …一个数据成员
name
,如果可能的话向其附加一些文本并且 - …一个数据成员
numbers
,push_back
如果可能,给它一些数字。
我真的建议您通过实施三个助手struct
来解决这个问题,如上所示。它没有那么多多余的输入,并使代码更清晰。
但是,如果您想忽略此建议,让我们看看如何使用宏来减少输入。假设和上图一样定义void_t
,我们可以定义如下宏
#define MAKE_SFINAE_HELPER(NAME, TYPE, OPERATION, ARGS, CODE) \
template<typename TYPE, typename = void> \
struct NAME \
{ \
template<typename... AnyT> \
void \
operator()(AnyT&&...) noexcept \
{ \
/* do nothing */ \
} \
}; \
\
template<typename TYPE> \
struct NAME<TYPE, void_t<decltype(std::declval<TypeT>()OPERATION)>> \
{ \
void operator()ARGS noexcept(noexcept(CODE)) \
{ \
CODE; \
} \
};
它将在类型参数 TYPE
上定义一个名为 NAME
的 struct
模板,并定义一个带有运算符 ()
的主模板,该模板接受任意数量的参数任何类型,什么都不做。如果不支持所需的操作,这将用作回退。
但是,如果类型 TYPE
的对象支持操作 OPERATION
,则带有运算符 ()
的偏特化采用参数 ARGS
并执行 CODE
将被使用。宏定义为 ARGS
可以是带括号的参数列表。不幸的是,预处理器语法只允许将单个表达式作为 CODE
传递。这不是一个大问题,因为我们总是可以编写一个委托给另一个函数的函数调用。 (请记住,计算机科学中的任何问题都可以通过添加额外的间接层来解决——当然,间接层太多的问题除外……)偏特化的运算符 ()
将被声明为 noexcept
当且仅当 CODE
是。 (这也只适用于 CODE
仅限于单个表达式。)
主模板的运算符 ()
是模板的原因是,否则编译器可能会发出有关未使用变量的警告。当然,您可以更改宏以接受附加参数 FALLBACK_CODE
,该参数位于主模板的运算符 ()
的主体中,该运算符应使用相同的 ARGS
。
在最简单的情况下,可以将 OPERATION
和 CODE
参数合并为一个参数,但 CODE
不能有效地引用 ARGS
将 ARGS
限制为 TYPE
类型的单个参数,在这种情况下,如果您不需要灵活性,也可以去掉该参数。
所以,让我们将其应用到我们的问题中。首先,我们需要一个辅助函数来推回数字,因为这不能(至少,让我们假装这样)写成单个表达式。我使此函数尽可能通用,仅对成员名称进行假设。
template<typename ObjT, typename NumT>
void
do_with_numbers(ObjT& obj, NumT num1, NumT num2, NumT num3)
{
obj.numbers.push_back(num1);
obj.numbers.push_back(num2);
obj.numbers.push_back(num3);
}
由于其他两个所需的操作可以很容易地写成单个表达式,因此我们不需要对它们进行进一步的间接寻址。所以现在,我们可以生成我们的 SFINAE 助手了。
MAKE_SFINAE_HELPER(HelperIncr,
TypeT,
.incr(),
(TypeT& obj),
obj.incr())
MAKE_SFINAE_HELPER(HelperName,
TypeT,
.name += "",
(TypeT& obj, const std::string& appendix),
obj.name += appendix)
MAKE_SFINAE_HELPER(HelperNumbers,
TypeT,
.numbers.push_back(0),
(TypeT& obj, int i1, int i2, int i3),
do_with_numbers(obj, i1, i2, i3))
有了这些,我们终于可以写出我们的frobnicate
函数了。真的很简单。
template<typename T>
void
frobnicate(T& object)
{
HelperIncr<T>()(object);
HelperName<T>()(object, "def");
HelperNumbers<T>()(object, 4, 5, 6);
}
为了确保一切正常,让我们制作两个 struct
来部分支持相关操作。
#include <string>
#include <vector>
struct Widget
{
std::vector<int> numbers {1, 2, 3};
int counter {};
void incr() noexcept { this->counter += 1; }
};
struct Gadget
{
std::string name {"abc"};
int counter {};
void incr() noexcept { this->counter += 1; }
};
因为我要打印它们,所以我们也定义运算符<<
。
#include <iostream>
std::ostream&
operator<<(std::ostream& os, const Widget& w)
{
os << "Widget : { counter : " << w.counter << ", numbers : [";
int i {};
for (const auto& v : w.numbers)
os << (i++ ? ", " : "") << v;
os << "] }";
return os;
}
std::ostream&
operator<<(std::ostream& os, const Gadget& g)
{
os << "Gadget : { counter : " << g.counter << ", "
<< "name = \"" << g.name << "\" }";
return os;
}
我们开始了:
int
main()
{
Widget widget {};
Gadget gadget {};
std::cout << widget << "\n" << gadget << "\n\n";
frobnicate(widget);
frobnicate(gadget);
std::cout << widget << "\n" << gadget << "\n";
}
输出:
Widget : { counter : 0, numbers : [1, 2, 3] }
Gadget : { counter : 0, name = "abc" }
Widget : { counter : 1, numbers : [1, 2, 3, 4, 5, 6] }
Gadget : { counter : 1, name = "abcdef" }
我鼓励您仔细衡量这种宏观方法的成本和收益。在我看来,额外的复杂性几乎不值得在打字上节省一点钱。
根据这个问题和其他问题的答案,我终于明白,我确实有两个问题需要两个不同的答案。
我有许多 classes 具有不同的成员,所有这些都具有以下类型的操作
::basedata::Maindata maindata;
maindata.subdata().push_back(subinfo);
auto inData maindata.subdata().back();
inData.contexts(contextInfo);
模板的形式是(假设 class subdata() 确实存在(例如问题中的 memberA)。
template<typename T, typename D>
auto doPush(D myData, T & myFormat, contextType contextInfo)
-> decltype(myFormat.push_back(myData), bool())
{
myFormat.push_back(myData);
setcontexts(myFormat.back(), contextInfo)
// Since the push-back() was already done, the new data gets entered
return true;
}
这会调用模板
doPush(dataset, maindata.subdata(), contextInfo);
由于这假设 subdata() 存在,我们现在需要为 subdata 表示的成员设置一个显式测试,并使其成为对通用模板调用的简单包装。
template<typename T, typename D>
auto createMember(D myData, T & myFormat, contextType contextInfo)
-> decltype(myFormat.Member(), bool())
{
dopush(myData, myFormat.Member(), myData);
return true;
}
请注意,如果有足够的位置需要一个,这只需要三个位置来通过宏输入成员名称。
然后实际代码将调用 createMember 模板。
这似乎是最简单的解决方案。
我没有显示虚假案例模板,因为它们很明显。