我阅读了一些用 C++ 创建的项目的技术文档。我发现了一行代码,其中包含我不明白的语法:
using c = char (& (cClass::* [1]) (cClass(*)[2]) &)[3];
Run Code Online (Sandbox Code Playgroud)
我在using
这里看到了关键字。这意味着我们要处理别名,但是这一行有什么作用呢?我怎么能理解呢?我认为这会创建一个命名别名c
并将右侧表达式的结果分配给它。但是这个表达是什么?
chi*_*chi 49
以下是如何阅读您的类型,一步一步。为了清楚起见,下面我们声明一个变量x
。
T x[1]
Run Code Online (Sandbox Code Playgroud)
x
是一个类型的数组(长度为 1) T
cClass::* x[1]
Run Code Online (Sandbox Code Playgroud)
x
是一个指向成员内部的指针数组(长度为 1)cClass
。
V (cClass::* x[1]) (U)
Run Code Online (Sandbox Code Playgroud)
x
是一个指向成员函数的指针数组(长度为 1)cClass
。所述成员函数U
作为参数并返回V
。
V (cClass::* x[1]) (U) &
Run Code Online (Sandbox Code Playgroud)
x
是一个指向成员函数的指针数组(长度为 1)cClass
。所述成员函数U
作为参数并返回V
,并且只能在左值上调用。
V & (cClass::* x[1]) (U) &
Run Code Online (Sandbox Code Playgroud)
x
是一个指向成员函数的指针数组(长度为 1)cClass
。所述成员函数U
作为参数并返回对-的引用V
,并且只能在左值上调用。
V (& (cClass::* x[1]) (U) &)[3]
Run Code Online (Sandbox Code Playgroud)
x
是一个指向成员函数的指针数组(长度为 1)cClass
。所述成员函数U
作为参数并返回对数组(长度为 3)的引用V
,并且只能在左值上调用。
最后,得到你的实际类型
char (& (cClass::* x[1]) (cClass(*)[2]) &)[3]
Run Code Online (Sandbox Code Playgroud)
我们选择V = char
, 和U = (cClass(*)[2])
,后者是指向数组(长度为 2)的指针cClass
。
更具可读性的替代方案:
using char3 = char [3];
using ptrTo2cClass = cClass(*)[2];
using ptrToMethod = char3 & (cClass::*) (ptrTo2cClass) &;
using c = ptrToMethod[1];
Run Code Online (Sandbox Code Playgroud)
Hol*_*Cat 22
必须从中间开始解释类型(如果它是变量声明,则变量名将在其中)。你向右走,直到你到达终点或 )
。然后你向左走,直到你到达开头或 (
。此时,您要么解释整个类型(然后完成),要么在 内部(...)
,在这种情况下,您丢弃它们并重复相同的过程(向右,然后向左)。
由于这不是变量声明,因此您首先需要找到变量名(如果它是变量名)的位置。凭直觉做更容易(一旦你获得了一些经验),但一个不错的经验法则是从左边走,直到你点击)
、 或(...)
、 或[...]
。
知道了这一点,我们可以把它变成一个变量声明。我添加x
了变量名:
char (& (cClass::*x[1]) (cClass(*)[2]) &)[3];
Run Code Online (Sandbox Code Playgroud)
现在我们可以开始读取类型了。首先我们向右看:
x
Variablex
是...
x[1]
一个包含 1 个元素的数组,包含...
(右边没有别的东西,往左边走。)
cClass::*x[1]
一个指向 class 成员的指针cClass
,类型...
(cClass::*x[1])
(没有别的东西到左边,跳过括号。)
(cClass::*x[1]) (............) &
一个带有一些参数的函数,&
-qualified 1,返回......
(右边没有其他东西,向左走。)
& (cClass::*x[1]) (............) &
对......的引用
(& (cClass::*x[1]) (............) &)
(左边没有其他东西,跳过括号。)
(& (cClass::*x[1]) (............) &)[3]
与3个元素的阵列,含有...
char (& (cClass::*x[1]) (............) &)[3]
char
小号
我们已经完成了。函数参数 ( cClass(*)[2]
)的类型必须使用相同的过程单独解释。它是“一个指向大小为 2 的数组的指针,包含cClass
es”。
1那&
对的函数参数的装置右侧它只能在左值称为(的功能是“ &
-qualified”)。
注意:这是对我在此代码行末尾的方括号是什么意思的回答的改编?因此我将其标记为社区维基以避免获得重复的声望点。(当然,如果您喜欢这个答案,请随时为该答案点赞。)
下面基于模板的 C++ 代码让编译器自己为您分解此声明的结构。
#include <iostream>
template <typename T>
struct introspect;
template <>
struct introspect<char> {
static std::ostream& prettyPrint(std::ostream& os) { return os << "char"; }
};
template <typename T>
struct introspect<T*> {
static std::ostream& prettyPrint(std::ostream& os) {
return os << "pointer to " << introspect<T>::prettyPrint;
}
};
template <typename T>
struct introspect<T&> {
static std::ostream& prettyPrint(std::ostream& os) {
return os << "reference to " << introspect<T>::prettyPrint;
}
};
template <typename T, typename Res, typename Arg1>
struct introspect<Res (T::*)(Arg1) &> {
static std::ostream& prettyPrint(std::ostream& os) {
return os << "pointer to lvalue member function of " << introspect<T>::prettyPrint
<< " taking (" << introspect<Arg1>::prettyPrint
<< ") and returning (" << introspect<Res>::prettyPrint << ')';
}
};
template <typename T, std::size_t N>
struct introspect<T[N]> {
static std::ostream& prettyPrint(std::ostream& os) {
return os << "array of " << N << " (" << introspect<T>::prettyPrint << ")";
}
};
class cClass;
template <>
struct introspect<cClass> {
static std::ostream& prettyPrint(std::ostream& os) {
return os << "cClass";
}
};
int main() {
using c = char (& (cClass::* [1]) (cClass(*)[2]) &)[3];
std::cout << introspect<c>::prettyPrint << '\n';
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出(为了便于阅读,手动添加了一些换行符):
array of 1 (pointer to lvalue member function of cClass taking
(pointer to array of 2 (cClass))
and returning
(reference to array of 3 (char)))
Run Code Online (Sandbox Code Playgroud)
缩小范围后,我才看到已经有两个答案。然而,我确实写了一个新答案,因为它显示了一种替代方法,您可以如何使用 STL 类型特征缩小范围,从外部到内部。
#include <type_traits>
#include <typeinfo>
#include <iostream>
class cClass {};
template <typename T>
struct Extract : std::false_type {};
template <typename TRet, typename TArg>
struct Extract<TRet (cClass::*)(TArg) &> : std::true_type {
using Ret = TRet;
using Arg = TArg;
};
int main()
{
using Type = char(&(cClass::* [1])(cClass(*)[2])&)[3];
std::cout << "Type=" << typeid(Type).name() << '\n';
static_assert(std::is_array_v<Type>);
using Type1 = decltype(std::declval<Type>()[0]);
std::cout << "array[" << std::extent_v<Type> << "] of " << typeid(Type1).name() << '\n';
static_assert(std::is_reference_v<Type1>);
using Type2 = decltype(std::remove_reference_t<Type1>());
std::cout << "ref to " << typeid(Type2).name() << '\n';
static_assert(std::is_member_function_pointer_v<Type2>);
std::cout << "member function pointer, cClass, taking " << typeid(Extract<Type2>::Arg).name() << ", returning " << typeid(Extract<Type2>::Ret).name() << '\n';
using FuncParam = cClass(*)[2]; // Pointer to array of 2 cClass
using FuncRet = char(&)[3]; // Reference to array of 3 chars
using Func = FuncRet(cClass::*)(FuncParam) &; // Member function for lvalue of cClass, taking FuncParam, returning FuncRet
using FuncArrayOne = Func[1]; // Array with one Func (WTF?)
static_assert(std::is_same_v<FuncArrayOne, Type>);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这写
Type=char (& __ptr64 (__cdecl cClass::*[1])(class cClass (* __ptr64)[2]) __ptr64& )[3]
array[1] of char (& __ptr64 (__cdecl cClass::*)(class cClass (* __ptr64)[2]) __ptr64& )[3]
ref to char (& __ptr64 (__cdecl cClass::*)(class cClass (* __ptr64)[2]) __ptr64& )[3]
成员函数指针,cClass,取class cClass (* __ptr64)[2],返回字符 [3]
要了解在我输出的每个步骤中需要哪个下一个函数,例如:
using Type_x = Type1;
std::cout << std::is_array_v<Type_x> << '\n';
std::cout << std::is_class_v<Type_x> << '\n';
std::cout << std::is_function_v<Type_x> << '\n';
std::cout << std::is_member_function_pointer_v<Type_x> << '\n';
std::cout << std::is_pointer_v<Type_x> << '\n';
std::cout << std::is_reference_v<Type_x> << '\n';
Run Code Online (Sandbox Code Playgroud)
1 和 0 提示我下一步该做什么。
A tricky part was on the member function pointer. First there is no std
type trait to extract the class, the return type and the parameter type(s). And then this was where the &
came in place that I failed to understand first. Gladly I knew that you can append &
and &&
to a member function, and the MSVC STL implements _is_member_function_pointer
via _Is_memfunptr
which gave me an additional hint how and where to add the &
.
My code stops here as I now understand all parts. If you wanted to go deeper, you could just repeat the steps.
When going back from inner to outer I added a using
declarations in each step, so the code gets much better understandable. Again there was one surprise: the outmost steps suggest that FuncArrayOne
should be an array of references to Func
, but that is not possible, so it is just an array of Func
.
To be sure that the results are correct there is a final static_assert
with std::is_same_v
.