使用 C++ 20 概念,我们可以这样写:
#define has_method(CLASS_NAME, METHOD_NAME) \ // Can be generalized of course, but it's just to illustrate the idea
[]<typename TEMPLATE_ARG>() \
{ \
return requires{ std::declval<TEMPLATE_ARG>().METHOD_NAME(); }; \
}.template operator()<CLASS_NAME>()
Run Code Online (Sandbox Code Playgroud)
然后像这样使用它:
int main()
{
struct S{
void call_1();
void call_2();
void call_3();
void call_4();
};
static_assert(has_method(S, call_1)); // OK
static_assert(has_method(S, call_2)); // OK
static_assert(has_method(S, call_3)); // OK
static_assert(has_method(S, call_4)); // OK
static_assert(has_method(S, call_5)); // Error
}
Run Code Online (Sandbox Code Playgroud)
有什么方法可以在has_method没有概念的情况下实现宏(即仅使用 C++17)?
更清楚地说:我需要一个可以接受类和方法名称的宏,我想在 constexpr 上下文中使用这样的宏,就像 constexpr 函数一样。标准 SFINAE 实现需要创建至少两个模板结构,这使得很难(或不可能)将所有这些都放入一个宏中。所以我的问题是否实际上是不可能的。
是的,这是可能的,尽管它确实需要宏 AFAIK,但您似乎并不介意。
我们当然需要一些 SFINAE 上下文,因为这是我们唯一允许编写语法错误的地方。但是创建就地模板很棘手,因为本地类不能包含它们,所以即使是if(class { /* template magic */}x; <something with x>)C++17 特性也无济于事。
从 C++14 开始,我知道有一个特性允许在表达式中使用模板化方法定义类型 - 模板化 lambdas。
我从std::visit使用重载的重载集合和SFINAE 测试中获得灵感:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
#define HAS_METHOD(class_type,method) \
overloaded { \
[](auto* arg, decltype(&std::decay_t<decltype(*arg)>::method) ptr) constexpr \
{ return true;}, \
[](auto* arg, ...) constexpr \
{ return false;} \
}((class_type*)nullptr,nullptr)
#include <iostream>
int main()
{
struct S{void call();};
if constexpr (HAS_METHOD(S,call))
std::cout << "S has a call\n";
else
std::cout << "S does not have a call\n";
if constexpr (HAS_METHOD(S,call2))
std::cout << "S has a call2\n";
else
std::cout << "S does not have a call2\n";
}
Run Code Online (Sandbox Code Playgroud)
S has a call
S does not have a call2
Run Code Online (Sandbox Code Playgroud)
基于此答案,可以将 SFINAE 建立在模板化函数之间的重载解析上。这个不错的特性是不需要专业化。
在这里,我将这种方法用于 lambda 的模板化operator(). 代码归结为这样的:
S has a call
S does not have a call2
Run Code Online (Sandbox Code Playgroud)
当我们调用时overload{...}((S*)nullptr,nullptr),从第一个参数T推导出来S。这有效地摆脱了模板化代码,同时仍处于 SFINAE 上下文中。第一个(辅助)参数是必需的,因为 lambdastemplate <typename S>在 C++20 之前没有,而且要获取类型,必须使用decltype(arg). std::decay_t是必需的,因为取消引用一个指针会返回一个引用并且T&::call永远不是有效的语法。
请注意,std::declval这里不能使用,因为要评估上下文。指针就是这样,我们实际上不会在任何地方取消引用它。现在
S::call有效,则第二个参数的类型为“指向具有call's 签名的成员函数的指针”。当然nullptr是任何指针的有效值。因为这个重载比...(任何有效的)更具体,所以它被选择并以某种方式true返回constexpr。S::call构成语法错误,第一个重载将被 SFINAE 丢弃,第二个仍然匹配,因为...将匹配nullptr并且仍然可以推导出第一个参数。在这种情况下,我们返回false。要在一个表达式中从 lambda 构建所需的一组重载,可以使用参数包扩展和方法继承,这正是std::visithelper中的这一行所做的:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
Run Code Online (Sandbox Code Playgroud)
然后宏本身只是构造了这个类的临时实例,ctor 用于初始化基类 = 传递 lambdas。之后,临时对象会立即被((S*)nullptr,nullptr)参数调用。
| 归档时间: |
|
| 查看次数: |
204 次 |
| 最近记录: |