用C ++重新解释:合法吗?

时间:2019-03-02 10:38:17

标签: c++ language-lawyer c++17 reinterpret-cast

这是一个有点深奥的问题,但是我很好奇以下类扩展模式在现代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可以在任何类型的指针/引用之间进行转换,但是访问那里得到的引用可能是UB(根据别名规则)。同时,将结果值转换回原始类型可确保产生原始值。

基于此,仅重新插入对float的引用作为对AddOne<float>的引用不会调用UB。由于我们从不尝试访问引用后面的任何内存作为AddOne<float>的实例,因此这里也没有UB。我们仅使用该引用的类型信息来选择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来派发重新解释的类型。

2 个答案:

答案 0 :(得分:10)

不,那绝对不合法。由于多种原因。

第一个原因是,您有*this取消引用了AddOne<int>*,而实际上并没有指向AddOne<int>。没关系,该操作实际上不需要在“幕后”取消引用; *foo仅在foo指向兼容类型的对象时才合法。

第二个原因类似:您正在AddOne<int>上调用成员函数,但不是。同样,不必访问AddOne的任何成员(不存在)也没关系:函数调用本身就是对对象值的访问,并且违反了严格的别名规则。

答案 1 :(得分:0)

完整的答案由@ n.m用户在评论中提供,因此出于完整性考虑,我将在此处复制。

[basic.life] c ++标准部分的一段指出:

  

在对象的生命周期开始之前[...]程序未定义   如果[...]   指针用于访问对象的非静态数据成员或调用对象的非静态成员函数

似乎,这禁止通过重新解释的引用进行分发,因为该引用未引用活动对象。