说我们有以下代码
struct MyClass
{
MyClass() = delete; // or MyClass() { }
MyClass(int) { }
void func() { }
};
int main()
{
if constexpr (std::is_default_constructible_v<MyClass>) {
MyClass myObj;
} else {
MyClass myObj(10);
}
myObj.func(); // Error
}
在这里,我使用if constexpr
来确定该类是否是默认可构造的(然后不是),然后相应地创建一个对象。从某种意义上说,我天真认为这会将不同的分支简化为正确的分支,即
if constexpr (true) {
/* instruction branch 1 */
} else if constexpr (false) {
/* instruction branch 2 */
}
简单地变成
/* instruction branch 1 */
但是实际上,它可能更像这样
{
/* instruction branch 1 */
}
但是接下来的问题变成了(回到第一个示例),如何将myObj
保留在{ ... }
之外的范围内?
答案 0 :(得分:3)
具有自动存储期限的对象的生存期不能超出其创建范围。
您可以做的是在if
块之外创建未初始化的存储,并在if
范围内的该存储中创建一个对象。最简单的方法可能是std::optional
:
template <typename T>
void foo() {
std::optional<T> obj;
if constexpr (std::is_default_constructible_v<T>) {
obj.emplace();
} else {
obj.emplace(10);
}
obj->func();
}
但这确实会导致少量开销,因为std::optional
必须持有一个额外的标志来确定它是否持有对象。如果您想避免这种开销,则可以自己管理存储:
template <typename T>
void foo() {
std::aligned_storage_t<sizeof(T), alignof(T)> storage;
T* ptr;
if constexpr (std::is_default_constructible_v<T>) {
ptr = new(&storage) T{};
} else {
ptr = new(&storage) T{10};
}
struct destroy {
destroy(T* ptr) : ptr_{ptr} {}
~destroy() { ptr_->~T(); }
T* ptr_;
} destroy{ptr};
ptr->func();
}
请注意,在两种情况下,我均已将功能移至功能模板。要使if constexpr
放弃分支,它必须取决于模板参数。如果您尝试直接在main
中执行此操作,则不会丢弃false分支,并且会因抱怨缺少默认构造函数而出错。
答案 1 :(得分:2)
首先,您的代码无效。 if constexpr
确实需要其条件依赖。
我会解决它。
template<class MyClass>
void func() {
MyClass myObj = []{
if constexpr (std::is_default_constructible_v<MyClass>) {
return MyClass{};
} else {
return MyClass(10);
}
}();
myObj.func();
}
现在
int main() {
func<MyClass>();
}
解决了您的问题。
请注意,根据c++17规则,以上代码中没有MyClass
的副本或移动。