当“失败是由在其生命周期之外读取变量引起的”时如何使 Constexpr 函数

问题描述

我有一个 struct 专门化的 enum Id我有一个 idOf 函数,它接受一个 Type<Id> 并返回模板参数。

我无法真正修改所有类型以包含额外的成员,并且我认为这必须非常简单地解决constexpr

当 Type 对象的初始化在同一个代码模块之外时,我遇到了一个问题,因为使用我的 constexpr 函数(在代码示例中作为注释嵌入),我遇到了编译错误。在示例中,我使用 getA() 方法返回引用来导致这种情况,但是 idOf() 实际上并不依赖于结构的实际内容,而只依赖于它的类型。

实时代码示例:https://godbolt.org/z/8f649T64c

#include <cstdint>

enum Id { A,B,C };

template<Id cId>
struct Type;

template<> 
struct Type<A> { int t; };

template<Id cId>
constexpr Id idOf( const Type<cId>& = {} )
{ return cId; }

Type<A>& getA();

int main( int argc,char**)
{
    Type<A>& a = getA();

    /// ERROR: error C2131: expression did not evaluate to a constant
    /// message: failure was caused by a read of a variable outside its lifetime
    /// message : see usage of 'data'
    constexpr Id id = idOf( a );

    return (int)id;
}

非常感谢帮助:D

编辑:最简单的解决方法是丑陋的,但会断开与范围之外形成的对象值的连接。这感觉像是一个完整的“黑客”,但我把它放在这里以供参考: constexpr Id id = idOf( std::decay_t<decltype(a)>() ) 这本质上是解析为 `id = idOf( Type() ) ,这与 constexpr 一样好。

解决方法

问题是你的参考。不允许在常量表达式中使用引用,除非:

引用变量或引用类型数据成员的 id 表达式,除非该引用可用于常量表达式(见下文)或其生命周期在此表达式的计算内开始

如果您将其从引用更改为值,没问题。


template<Id cId>
constexpr std::integral_constant<Id,cId> idOf( const Type<cId>& = {} )
{ return {}; }

这里我对类型中的返回值进行编码。

constexpr Id id = decltype(idOf( a ))::value;

我在这里提取它。

Live example

另一种使用标签的方法:

template<class T>
struct tag_t {using type=T;};
template<class T>
constexpr tag_t<T> tag{};
template<class T>
constexpr tag_t<std::decay_t<T>> dtag{};

然后我们添加:

template<Id cId>
constexpr std::integral_constant<Id,cId> idOf( tag_t<Type<cId>> = {} )
{ return {}; }

我们可以这样做:

constexpr Id id2 = idOf(dtag<decltype( a )>);

Live example


题外话:

tag_t 很有用,因为它允许您将类型作为值传递。所以我可以在没有模板 lambda 支持的情况下将类型传递给 lambda,或者存储类型的变体(不是该类型的值,而是类型)。

所以它不是一次性的,而是在其他地方有用的东西。例如:

template<class...Ts>
using types = std::variant<tag_t<Ts>...>;
template<class...Ts>
constexpr types<Ts...> get_type( std::variant<Ts...> const& v ) {
  constexpr types<Ts...> table[] = {
    tag<Ts>...
  };
  if (v.valueless_by_exception())
    throw std::bad_variant_access{};
  return table[v.index()];
}

这里我只是对 variant 上的类型进行了枚举,可以使用 std::visit 将其转换回类型本身。

,

我得到的最接近的解决方案是使用模板对象来确定 Id 而不是使用 constexpr 表达式:

template <typename cId> 
struct IdOf;

template <Id cId >
struct IdOf< Type<cId> > : std::integral_constant<Id,cId> {};

template<typename T>
constexpr Id idOf = IdOf< std::decay_t<T> >::value;

这会将调用方站点更改为:

constexpr Id id = idOf<decltype(a)>;

这还不错,但对我来说仍然感觉像是一种“解决方法”,除非此问题确定了 constexpr

的实际限制

在这里实时编译代码:https://godbolt.org/z/s5bTTdqW9

编辑:添加了 idOf<>