类型擦除工作或失败取决于优化级别

Type erasure works or fails depending on optimization level

我正在尝试通过类型擦除将值类型包装在包装器中(作为简单格式化文本输出库的一部分)。下面的函数 print 应该采用包装在类型擦除包装器结构中的参数,该结构知道(通过函数指针)如何将其转换为字符串并打印出来。

当我编译它时它打印 0(或者有时是垃圾):

g++ -std=c++11 -Wall -Wextra -O0 -o test test.cpp

但是当使用 -O1-O3 编译时它按预期工作(-Og 也失败)。使用 clang++,它的行为正好相反(启用优化时它会失败)。我还尝试了 g++ -m32(我的 x86_64 Linux Mint box 上有 multilib gcc)以及 32 位和 64 位 mingw-w64 交叉编译器。行为是相似的。另外,clang++-libc++ 似乎总是失败。

我一定是触发了一些未定义的行为(因为我发现 g++ 和 clang++ 不太可能有相同的错误)。怎么回事,我错过了什么?

#include <iostream>
#include <string>

using namespace std;

// Struct holding a pointer to a type-erased value and a function pointer to
// convert it to a string
struct TypeErasingWrapper {
    void* item;                 // Pointer to type erased value
    string (*to_string)(void*); // Function pointer to convert it to a string
};

// Convert any value pointer to a string (using the proper overload of
// std::to_string
template <typename T>
string toString (void* item)
{
    return to_string(*reinterpret_cast<T*>(item));
}

// Wrap any value in a type-erasing wrapper
template <typename T>
TypeErasingWrapper wrap(T value) {
    return {&value, toString<T>};
}

// Print a type erased value
void print(TypeErasingWrapper wrapper)
{
    cout << wrapper.to_string(wrapper.item) << endl;
}

int main() 
{
    print(wrap(1234));
}

这是没有模板的版本,其行为方式相同。

#include <iostream>
#include <string>

using namespace std;

// Struct holding a pointer to a type-erased int and a function pointer to
// convert it to a string
struct TypeErasingWrapper {
    void* item;                 // Pointer to type erased int
    string (*to_string)(void*); // Function pointer to convert it to a string
};

// Convert type-erased int to a string
string toString (void* item)
{
    return to_string(*reinterpret_cast<int*>(item));
}

// Wrap an int in a type-erasing wrapper
TypeErasingWrapper wrap(int value) {
    return {&value, toString};
}

// Print a type erased value
void print(TypeErasingWrapper wrapper)
{
    cout << wrapper.to_string(wrapper.item) << endl;
}

int main() 
{
    print(wrap(1234));
}
template <typename T>
TypeErasingWrapper wrap(T value) {
  return {&value, toString<T>};
}

您按值取 value。然后将指向它的指针传递给 return 值。

value只持续到函数体结束,此时指针变成悬空指针。

更改 TypeErasingWrapper 以存储 void const*。将 wrap 更改为 const&Ttemplate<class T> std::string toString( void const * ) 还有。修复剩余的构建错误。将 reinterpret_cast 更改为 static_cast

典型的类型擦除代码也会擦除所有权(销毁、移动,有时是复制)以处理生命周期问题。如果你不这样做,我建议你调用你的类型 blah_view 来让最终用户清楚它不是真正的值类型。

作为最后的评论,在文件范围内停止 using namespace std;。简洁是不值得的。