用 C++ 重新解释:合法与否?
Reinterpret this in C++: legal or not?
这是一个有点深奥的问题,但我很好奇以下 class 扩展模式在现代 C++ 中是否合法(例如,不构成 UB)(对于所有意图和目的我都很好将讨论限制在 C++17 及更高版本)。
template<typename T>
struct AddOne {
T add_one() const {
T const& tref = *reinterpret_cast<T const*>(this);
return tref + 1;
}
};
template<template<typename> typename E, typename T>
E<T> const& as(T const& obj) {
return reinterpret_cast<E<T> const&>(obj);
}
auto test(float x) {
return as<AddOne>(x).add_one();
}
auto test1(int x) {
return as<AddOne>(x).add_one();
}
// a main() to make this an MVCE
// will return with the exit code 16
int main(int argc, const char * argv[]) {
return test1(15);
}
以上代码是一个完整的示例,它在C++17模式下编译、运行并产生了至少clang的预期结果。在编译器资源管理器上查看反汇编代码:https://godbolt.org/z/S3ZX2Y
我的解释如下:标准规定 reinterpret_cast
可以在任何类型的 pointers/references 之间转换,但是访问那里产生的引用可能是 UB(根据别名规则)。同时,将结果值转换回原始类型可以保证产生原始值。
基于此,仅将对 float
的引用重新截取为对 AddOne<float>
的引用并不会调用 UB。由于我们从不尝试访问该引用后面的任何内存作为 AddOne<float>
的实例,因此这里也没有 UB。我们仅使用该引用的类型信息 select add_one()
成员函数的正确实现。该函数本身将引用转换回原始类型,因此同样没有 UB。本质上,这个模式在语义上等同于:
template<typename T>
struct AddOne {
static T add_one(T const& x) {
return x + 1;
}
};
auto test(float x) {
return AddOne<Int>::add_one(x);
}
我说的对吗?还是我漏掉了什么?
将此视为探索 C++ 标准的学术练习。
编辑:这不是 When to use reinterpret_cast? 的副本,因为该问题不讨论转换 this
指针或使用 reinterpret_cast
对重新解释的类型进行分派。
不,这绝对不合法。出于多种原因。
第一个原因是,您 *this
取消引用了一个 AddOne<int>*
,它实际上并不指向 AddOne<int>
。该操作实际上不需要取消引用并不重要"behind the scenes"; *foo
仅在 foo
指向兼容类型的对象时才合法。
第二个原因类似:您在 AddOne<int>
上调用一个成员函数,但实际上不是。同样,您不访问任何 AddOne
的(不存在的)成员也没关系:函数调用本身是对对象值的访问,运行 违反了严格的别名规则。
完整的答案由用户@n.m在评论中提供,为了完整起见,我将其复制在这里。
[basic.life] c++ 标准部分中的一段指出:
Before the lifetime of an object has started [...] The program has undefined
behavior if [...]
the pointer is used to access a non-static data member or call a non-static member function of the object
看起来,这禁止通过重新解释的引用进行分派,因为该引用不引用活动对象。
这是一个有点深奥的问题,但我很好奇以下 class 扩展模式在现代 C++ 中是否合法(例如,不构成 UB)(对于所有意图和目的我都很好将讨论限制在 C++17 及更高版本)。
template<typename T>
struct AddOne {
T add_one() const {
T const& tref = *reinterpret_cast<T const*>(this);
return tref + 1;
}
};
template<template<typename> typename E, typename T>
E<T> const& as(T const& obj) {
return reinterpret_cast<E<T> const&>(obj);
}
auto test(float x) {
return as<AddOne>(x).add_one();
}
auto test1(int x) {
return as<AddOne>(x).add_one();
}
// a main() to make this an MVCE
// will return with the exit code 16
int main(int argc, const char * argv[]) {
return test1(15);
}
以上代码是一个完整的示例,它在C++17模式下编译、运行并产生了至少clang的预期结果。在编译器资源管理器上查看反汇编代码:https://godbolt.org/z/S3ZX2Y
我的解释如下:标准规定 reinterpret_cast
可以在任何类型的 pointers/references 之间转换,但是访问那里产生的引用可能是 UB(根据别名规则)。同时,将结果值转换回原始类型可以保证产生原始值。
基于此,仅将对 float
的引用重新截取为对 AddOne<float>
的引用并不会调用 UB。由于我们从不尝试访问该引用后面的任何内存作为 AddOne<float>
的实例,因此这里也没有 UB。我们仅使用该引用的类型信息 select add_one()
成员函数的正确实现。该函数本身将引用转换回原始类型,因此同样没有 UB。本质上,这个模式在语义上等同于:
template<typename T>
struct AddOne {
static T add_one(T const& x) {
return x + 1;
}
};
auto test(float x) {
return AddOne<Int>::add_one(x);
}
我说的对吗?还是我漏掉了什么?
将此视为探索 C++ 标准的学术练习。
编辑:这不是 When to use reinterpret_cast? 的副本,因为该问题不讨论转换 this
指针或使用 reinterpret_cast
对重新解释的类型进行分派。
不,这绝对不合法。出于多种原因。
第一个原因是,您 *this
取消引用了一个 AddOne<int>*
,它实际上并不指向 AddOne<int>
。该操作实际上不需要取消引用并不重要"behind the scenes"; *foo
仅在 foo
指向兼容类型的对象时才合法。
第二个原因类似:您在 AddOne<int>
上调用一个成员函数,但实际上不是。同样,您不访问任何 AddOne
的(不存在的)成员也没关系:函数调用本身是对对象值的访问,运行 违反了严格的别名规则。
完整的答案由用户@n.m在评论中提供,为了完整起见,我将其复制在这里。
[basic.life] c++ 标准部分中的一段指出:
Before the lifetime of an object has started [...] The program has undefined behavior if [...] the pointer is used to access a non-static data member or call a non-static member function of the object
看起来,这禁止通过重新解释的引用进行分派,因为该引用不引用活动对象。