无效的模板实例化和元程序编译良好

Invalid template instantation and the metaprogram compiles fine?

本文关键字:程序 编译 实例化 无效      更新时间:2023-10-16

我正在研究一个简单的解决方案来解决常见的"病态类型的条件"问题(就像昨天的问题)。

在我的代码库中,我有一个模板来保存未实例化的模板,并在以后实例化它们。像这样:
template<template<typename...> class F>
struct lazy
{};
namespace impl
{
    template<typename L , typename... ARGS>
    struct lazy_instance;
    template<template<typename...> class F , typename... ARGS>
    struct lazy_instance<lazy<F>,ARGS...> : public identity<F<ARGS...>>
    {};
}
template<typename L , typename... ARGS>
using lazy_instance = typename impl::lazy_instance<L,ARGS...>::type;

其中identity为恒等元函数。
可以这样使用:

using vector = lazy<std::vector>;
using int_vector = lazy_instance<vector,int>;

因此,我想到的解决方案是以这种方式包装条件的两个目标,并实例化条件的结果,因此只有选定的模板被实例化。为此,我修改了lazyimpl::lazy_instance,以允许我们在lazy实例点传递模板参数:

template<template<typename...> class F , typename... ARGS>
struct lazy
{};
namespace impl
{
    template<typename L , typename... ARGS>
    struct lazy_instance;
    template<template<typename...> class F , typename... ARGS , typename... IARGS>
    struct lazy_instance<lazy<F,ARGS...>,IARGS...> : public identity<F<ARGS...>>
    {};
}

注意,在这种情况下,lazy_instance也接受模板参数,但是它们被忽略了。我这样做是为了在两个用例中使用相同的界面。

所以我们的主要问题,潜在病态类型的条件选择的评估可以通过以下方式实现:

using ok = lazy_instance<typename std::conditional<true,
                                                   lazy<foo,int>,
                                                   lazy<foo,bool>
                                                  >::type
                        >;

其中foo是模板,其中bool实例是不正确的,例如:

template<typename T>
struct foo;
template<>
struct foo<int>
{};

它似乎有效,不是吗?太好了。但是几分钟后,我把布尔标志改为false,令人惊讶的是它也起作用了!即使没有定义foo<bool>专门化!

元程序下面也有一个static_assert来检查条件的求值是否成功:

static_assert( std::is_same<ok,foo<int>>::value , "Mmmmm..." );

当我将条件从true更改为false时,我更改了测试以查看发生了什么:

static_assert( std::is_same<ok,foo<bool>>::value , "Mmmmm..." );

并且元程序也通过了测试!即使foo<bool>ok的显式实例化,也应该是该实例的别名。
最后,我检查了没有"foo<bool>没有匹配的专门化",直到我声明类型为ok的变量:ok anok;

我正在使用CLang 3.4,在c++ 11模式(-std=c++11)上工作。我的问题是:这里发生了什么?这种行为是正确的还是LLVM错误?

EDIT:这是一个运行在ideone上的SSCCE。它使用GCC 4.8.1,但似乎具有相同的行为

说来话长;短。

您实际上没有在下面的表达式中实例化foo<bool>:

std::is_same<ok, foo<bool>>::value;

隐式实例化何时发生?

14.7.1 隐含实例化 [templ.inst]

5)如果类类型在需要完全定义的对象类型的上下文中使用,或者类类型的完整性可能影响程序的语义,则隐式实例化类模板专门化。

7)如果需要类模板专门化的隐式实例化,并且声明了模板但没有定义模板,则程序是病态的。

上面的文本说的是,类模板只有在需要完全定义的上下文中使用时才会隐式实例化,例如当声明所述模板的对象时,或者当试图访问其中的某些内容时。

检查一个类型是否与另一个类型相同不算作这样的上下文,因为我们只是比较两个名称


何时需要完全定义的对象?

标准在几个不同的地方引用了"完全定义的",主要是当它明确地说需要这样一个对象时。

何时需要完全定义的对象,最简单的定义是阅读下面的内容,它解释了它不是什么。

3.9p5 Types [basic.types]

已声明但未定义的类、未知大小的数组或元素类型不完整的数组是未完全定义的对象类型。不完全定义的对象类型和void类型是不完全类型(3.9.1)。对象不能定义为不完整类型。

上面的措辞表明,只要我们不声明一个对象为不完整类型,我们就没有问题;ie。我们的模板不会被隐式实例化。

参见下面的例子,其中(C)和(D)试图创建一个不完整类型的对象, (A)和(B)都是合法的,因为它们不会导致隐式实例化

template<class T> struct A;
typedef A<int> A_int; // (A), legal
A<int> *     ptr;     // (B), legal
A<int>       foo;     // (C), ill-formed; trying to declare an object of incomplete-type
A<int>::type baz;     // (D), ill-formed; trying to reach into the definition of `A<int>`