const_cast 和 std::move 从非引用中删除常量

const_cast and std::move to remove constness from non-reference

我有一个无法修改的外部库。该库声明了一个模板函数,由于某种原因 returns const 非引用对象:

template<class C>
const C foo();

我还有另一个外部库,我也无法修改。该库声明了一个不可复制的 class,并且仅具有来自非常量对象的移动构造函数:

struct bar {
    bar();
    bar(const bar&)=delete;
    bar(bar&&);
};

现在我需要使用foo<bar>。一个简单的用法:

bar buz() {
    return foo<bar>();
}

失败

main.cpp: In function 'bar buz()':
main.cpp:13:21: error: use of deleted function 'bar::bar(const bar&)'
     return foo<bar>();
                     ^
main.cpp:8:5: note: declared here
     bar(const bar&)=delete;
     ^~~

这是有道理的,没有简单的解决方法可以使代码编译。

但是,如果我添加一些更复杂的解决方法:

bar buz() {
    return const_cast<bar&&>(std::move(foo<bar>()));
}

它编译并且整个代码按预期工作(不仅是上面的简化示例,还有我的真实代码)。

但是,它是安全的,还是我 运行 陷入了某种未定义的行为?有没有更好的解决方法?


我已阅读并理解有关从函数 (1, 2) 返回 const 的问题,常见的答案似乎是现代 C++ 不鼓励返回 const 对象,但我的问题不是关于它,而是关于如何解决外部库 returns const 对象时的情况。

从技术上讲,您将程序暴露给未定义的行为。由于原始对象 C(一个临时对象)被声明为 const,const 转换和修改它是非法的并且违反了标准。 (我假设,移动构造函数对 movee 做了一些修改)。

话虽这么说,它可能适用于您的环境,但我没有看到更好的解决方法。

根据定义,函数调用的结果本身就是一个 R 值,因此您无需在 return 语句中对其应用 std::move - const_cast<bar&&>(foo<bar>()) 就足够了。这使代码更易于阅读。

仍然 - 没有标准保证这将始终适用于所有 bar 类型。甚至更多 - 这在某些情况下可能会导致未定义的行为。(想象一些非常侵入性的优化,它完全根除 foo 并使其结果成为 "static data" 内存段中的对象 - 就像 foo 是一个 constexpr。然后调用可能会修改其参数的移动构造函数,可能会导致访问冲突异常)。

你所能做的就是切换到不同的库(或者,如果可能的话,请库维护者修复 API)或者创建一些单元测试并将其包含在你的构建过程中——只要测试通过,你应该没问题(记住使用与 "production" 构建中相同的优化设置 - const_cast 是其中一个强烈依赖于编译设置的东西)。

如果 bar 的移动构造函数修改了任何内容,则放弃 const 将导致未定义的行为。你可以像这样解决你的问题而不引入未定义的行为:

struct wrapped_bar {
    mutable bar wrapped;
};

bar buz()
{
    return foo<wrapped_bar>().wrapped;
}

使 wrapped 成员可变意味着该成员是非常量,即使 wrapped_bar 对象作为一个整体是常量。根据 foo() 的工作方式,您可能需要向 wrapped_bar 添加成员以使其更像 bar