"failure was caused by a read of a variable outside its lifetime" 时如何使 Constexpr 函数

How to make Constexpr function when "failure was caused by a read of a variable outside its lifetime"

我有一个 enum Id 专门化的 struct。我有一个 idOf 函数,它采用 Type<Id> 和 returns 模板参数。

我真的无法修改所有类型以包含一个额外的成员,并认为这必须非常简单才能解决 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

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

一样好

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

an id-expression referring to a variable or a data member of reference type, unless the reference is usable in constant expressions (see below) or its lifetime began within the evaluation of this expression

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


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

这里我将 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)>;

它还不错,但对我来说仍然感觉像 'workaround',除非这个问题确定了 constexpr

的实际限制

在此处实时编译代码:https://godbolt.org/z/s5bTTdqW9

编辑:添加了 idOf<>