如何在C++中编写工厂函数初始化constexpr引用?是否可行?
这确实是个很实际的痛点——想用工厂函数省掉重复写类型参数,还能靠重载选合适的类,但偏偏constexpr引用这儿卡壳了。本质上这和C++对constexpr引用的绑定规则、以及静态存储期对象的要求脱不开关系,咱们来拆解清楚:
核心问题:constexpr引用的绑定规则
C++里,constexpr引用必须绑定到具有静态存储期的constexpr对象,或者是被延长生命周期的constexpr临时对象——但后者的生命周期延长只在当前表达式内有效,工厂函数返回的临时对象引用根本没法带出函数,一返回就悬空了,自然不符合constexpr的要求。
而你直接调用构造函数能成,是因为:
- 如果是
constexpr MyClass obj{}; constexpr auto& ref = obj;,obj是静态存储期的constexpr变量,引用绑定完全合法; - 哪怕是
constexpr auto& ref = MyClass{};,临时对象的生命周期会被引用延长,而且是constexpr构造的,也符合规则。
但工厂函数的情况不一样:
- 要是工厂返回临时对象的引用:函数结束后临时对象销毁,引用直接悬空,constexpr语境下这种情况直接被编译器拒绝;
- 要是工厂返回函数内静态对象的引用:C++17之后静态局部变量可以是constexpr,但要让工厂成为constexpr函数,这个静态对象的初始化必须是constexpr,而且每次调用返回同一个引用——如果你的重载是要返回不同类型的对象,那得给每个重载对应一个静态的constexpr实例才行。
可行的解决方案
方案1:工厂返回对象,再绑定引用
最简单的思路是让工厂函数返回对象而非引用,之后你再把这个对象绑定到constexpr引用上:
class MyClass { public: constexpr MyClass(int) {} }; constexpr MyClass make_my_class(int val) { return MyClass{val}; } // 使用 constexpr auto obj = make_my_class(42); constexpr auto& ref = obj;
这样obj是静态存储期的constexpr对象,引用绑定完全合法,而且也达到了不用写类型参数的目的——编译器会自动推导返回类型。
方案2:工厂返回静态存储期对象的引用
如果必须让工厂返回引用,那可以预先在全局/命名空间级别定义好各个类型的constexpr实例,然后让重载的工厂函数返回对应实例的引用:
class MyClass {}; class MyOtherClass {}; constexpr MyClass obj_for_int{}; constexpr MyOtherClass obj_for_double{}; constexpr const MyClass& make_obj(int) { return obj_for_int; } constexpr const MyOtherClass& make_obj(double) { return obj_for_double; } // 使用 constexpr auto& ref1 = make_obj(10); // 绑定到MyClass实例 constexpr auto& ref2 = make_obj(3.14); // 绑定到MyOtherClass实例
这种方式完美满足你的需求:不用写类型参数,靠重载选对应类,而且返回的引用指向静态存储期的constexpr对象,符合constexpr引用的要求。
方案3:C++17+ 用静态局部constexpr对象
如果不想搞全局实例,C++17允许在constexpr函数里定义静态局部constexpr对象,工厂函数可以返回这个对象的引用:
template<typename T> constexpr const T& make_obj() { static constexpr T instance{}; return instance; } // 或者重载版本 constexpr const MyClass& make_obj(int) { static constexpr MyClass instance{}; return instance; } constexpr const MyOtherClass& make_obj(double) { static constexpr MyOtherClass instance{}; return instance; } // 使用 constexpr auto& ref = make_obj<MyClass>(); // 或者 constexpr auto& ref = make_obj(42);
这里静态局部变量是constexpr初始化的,具有静态存储期,返回的引用完全符合constexpr引用的绑定规则。
总结
不是没法实现,而是要严格遵守constexpr引用的绑定要求:必须绑定到静态存储期的constexpr对象。根据你的需求选上面的方案就行,要么让工厂返回对象再绑定引用,要么让工厂返回预先定义/静态局部的constexpr对象的引用。
内容的提问来源于stack exchange,提问作者Marcel

