编译时模板实例化检查

2021-12-13 00:00:00 templates c++ c++11 typetraits

是否可以在编译时检查模板类型是否已实例化,以便我可以在 enable_if 专业化中使用此信息?

假设我有

template 结构已知类型{};

如果 known_type 在编译时实例化,我可以以某种方式定义一些值为 true 的 is_known_type 吗?

解决方案

如果您利用特定表达式可能会或可能不会在需要 constexpr 的地方使用这一事实,则可以这样做,并且您可以查询以查看您拥有的每个候选人的状态.特别是在我们的例子中,没有定义的 constexpr 不能作为常量表达式传递,而 noexcept 是常量表达式的保证.因此,noexcept(...) 返回 true 表示存在正确定义的 constexpr.

本质上,这将 constexprs 视为 Yes/No 开关,并在编译时引入状态.

请注意,这几乎是一个 hack,您将需要针对特定??编译器的变通方法(请参阅前面的文章),并且此特定的基于 friend 的实现可能会被未来的修订版视为格式错误标准.

除此之外...

用户 Filip Roséen 在 他的文章专门针对它.

他的示例实现是,带有引用的解释:

constexpr int flag (int);

<块引用>

constexpr 函数可以处于两种状态之一;要么是可用于常量表达式,或者不是 - 如果它缺少定义它自动属于后一类 - 没有其他状态(除非我们考虑未定义的行为).

通常,constexpr 函数应该完全按照它们的方式来对待.是;函数,但我们也可以将它们视为单独的句柄具有类似于 bool 类型的变量",其中每个变量"都可以具有两个值之一;可用或不可用.

在我们的程序中,如果您认为 flag 就是这样,它会有所帮助;一个手柄(不是函数).原因是我们永远不会真正调用 flag在评估上下文中,我们只对其当前状态感兴趣.

template结构作者{朋友 constexpr int 标志(标签){返回0;}};

<块引用>

writer 是一个类模板,它在实例化时会创建一个函数在其周围命名空间中的定义(具有签名 int 标志(Tag),其中 Tag 是模板参数).

如果我们再次将 constexpr 函数视为某些变量,我们可以将 writer 的实例化视为无条件地将可用值写入后面的变量朋友声明中的函数.

templatestructdependent_writer : writer{ };

<块引用>

如果你认为dependent_writer 看起来像一个相当无意义的间接;为什么不直接实例化writer我们想在哪里使用它,而不是通过dependent_writer?

  1. writer 的实例化必须依赖某些东西来防止立即实例化,并且;
  2. dependent_writer 用于可以将 bool 类型的值用作依赖项的上下文.

模板

<块引用>

上面可能看起来有点奇怪,但其实很简单;

  1. 如果 flag(0) 是一个常量表达式,将设置 B = true,否则 B = false,并且;
  2. 隐式实例化dependent_writer(sizeof 需要完全定义的类型).

行为可以用以下伪代码表示:

IF [ `int flag (int)` 尚未定义 ]:SET `B` = `false`实例化`dependent_writer`返回`假`别的:SET `B` = `true`实例化`dependent_writer`返回`真`

最后是概念证明:

int main() {constexpr int a = f();constexpr int b = f();static_assert (a != b, "fail");}

<小时>

我将此应用于您的特定问题.这个想法是使用 constexpr Yes/No 开关来指示一个类型是否已经被实例化.因此,您需要为您拥有的每种类型设置一个单独的开关.

templatestruct inst_check_wrapper{朋友 constexpr int inst_flag(inst_check_wrapper<T>);};

inst_check_wrapper<T> 本质上为您提供的任何类型包装了一个开关.这只是原始示例的通用版本.

template结构作者{朋友 constexpr int inst_flag(inst_check_wrapper<T>){返回0;}};

开关切换器与原始示例中的切换器相同.它提出了您使用的某种类型的开关的定义.为了便于检查,添加一个辅助开关检查器:

template ()))>constexpr bool is_instantiated(){返回 B;}

最后,类型注册"为初始化.就我而言:

template 结构体{模板 >)>我的结构(){}};

只要请求特定的构造函数,开关就会打开.示例:

int main(){static_assert(!is_instantiated>(), "failure");MyStruct一个;static_assert(is_instantiated>(), "failure");}

在 Coliru 上直播.

Is it possible to check if a template type has been instantiated at compile time so that I can use this information in an enable_if specialization?

Let's say I have

template <typename T> struct known_type { };

Can I somehow define some is_known_type whose value is true if known_type is instantiated at compile time?

解决方案

It's possible to do this if you leverage the fact that specific expressions may or may not be used in places where constexprs are expected, and that you can query to see what the state is for each candidate you have. Specifically in our case, the fact that constexprs with no definition cannot pass as constant expressions and noexcept is a guarantee of constant expressions. Hence, noexcept(...) returning true signals the presence of a properly defined constexpr.

Essentially, this treats constexprs as Yes/No switches, and introduces state at compile-time.

Note that this is pretty much a hack, you will need workarounds for specific compilers (see the articles ahead) and this specific friend-based implementation might be considered ill-formed by future revisions of the standard.

With that out of the way...

User Filip Roséen presents this concept in his article dedicated specifically to it.

His example implementation is, with quoted explanations:

constexpr int flag (int);

A constexpr function can be in either one of two states; either it is usable in a constant-expression, or it isn't - if it lacks a definition it automatically falls in the latter category - there is no other state (unless we consider undefined behavior).

Normally, constexpr functions should be treated exactly as what they are; functions, but we can also think of them as individual handles to "variables" having a type similar to bool, where each "variable" can have one of two values; usable or not-usable.

In our program it helps if you consider flag to be just that; a handle (not a function). The reason is that we will never actually call flag in an evaluated context, we are only interested in its current state.

template<class Tag>
struct writer {
  friend constexpr int flag (Tag) {
    return 0;
  }
};

writer is a class template which, upon instantiation, will create a definition for a function in its surrounding namespace (having the signature int flag (Tag), where Tag is a template-parameter).

If we, once again, think of constexpr functions as handles to some variable, we can treat an instantiation of writer as an unconditional write of the value usable to the variable behind the function in the friend-declaration.

template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };

I would not be surprised if you think dependent_writer looks like a rather pointless indirection; why not directly instantiate writer where we want to use it, instead of going through dependent_writer?

  1. Instantiation of writer must depend on something to prevent immediate instantiation, and;
  2. dependent_writer is used in a context where a value of type bool can be used as dependency.

template<
  bool B = noexcept (flag (0)),
  int    =   sizeof (dependent_writer<B>)
>
constexpr int f () {
  return B;
}

The above might look a little weird, but it's really quite simple;

  1. will set B = true if flag(0) is a constant-expression, otherwise B = false, and;
  2. implicitly instantiates dependent_writer (sizeof requires a completely-defined type).

The behavior can be expressed with the following pseudo-code:

IF [ `int flag (int)` has not yet been defined ]:
  SET `B` =   `false`
  INSTANTIATE `dependent_writer<false>`
  RETURN      `false`
ELSE:
  SET `B` =   `true`
  INSTANTIATE `dependent_writer<true>`
  RETURN      `true`

Finally, the proof of concept:

int main () {
  constexpr int a = f ();
  constexpr int b = f ();
  static_assert (a != b, "fail");
}


I applied this to your particular problem. The idea is to use the constexpr Yes/No switches to indicate whether a type has been instantiated. So, you'll need a separate switch for every type you have.

template<typename T>
struct inst_check_wrapper
{
    friend constexpr int inst_flag(inst_check_wrapper<T>);
};

inst_check_wrapper<T> essentially wraps a switch for whatever type you may give it. It's just a generic version of the original example.

template<typename T>
struct writer 
{
    friend constexpr int inst_flag(inst_check_wrapper<T>) 
    {
        return 0;
    }
};

The switch toggler is identical to the one in the original example. It comes up with the definition for the switch of some type that you use. To allow for easy checking, add a helper switch inspector:

template <typename T, bool B = noexcept(inst_flag(inst_check_wrapper<T>()))>
constexpr bool is_instantiated()
{
    return B;
}

Finally, the type "registers" itself as initialized. In my case:

template <typename T>
struct MyStruct
{
    template <typename T1 = T, int = sizeof(writer<MyStruct<T1>>)>
    MyStruct()
    {}
};

The switch is turned on as soon as that particular constructor is asked for. Sample:

int main () 
{
    static_assert(!is_instantiated<MyStruct<int>>(), "failure");
    MyStruct<int> a;
    static_assert(is_instantiated<MyStruct<int>>(), "failure");
}

Live on Coliru.

相关文章