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

时间:2014-06-08 13:11:57

标签: c++ templates c++11 template-meta-programming llvm-clang

我正在研究一个简单的解决方案,解决常见的“有条件的错误类型”问题(如this昨天的问题)。

在我的代码库中,我有一个模板来保存未实例化的模板并在以后实例化。像这样:

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
                        >;

其中foobool即时消息格式错误的模板,例如:

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错误?

编辑: Here是在ideone上运行的SSCCE。它使用GCC 4.8.1,但似乎它具有相同的行为。

1 个答案:

答案 0 :(得分:6)

长篇故事;短。

您实际上并未在以下表达式中实例化foo<bool>

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

隐式实例化何时发生?

  

14.7.1 隐式实例化 [templ.inst]

     
    

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

         

7)如果需要隐式实例化类模板特化并且声明模板但未定义模板,则程序格式不正确。

  

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

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


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

标准在几个不同的位置引用“完全定义的”,主要是当它明确表示需要这样的对象时。

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

  

3.9p5 类型 [basic.types]

     
    

已声明但未定义的类,或者未知大小或元素类型不完整的数组,是一个未完全定义的对象类型。未完全定义的对象类型和void类型是不完整的类型(3.9.1)。对象不应被定义为具有不完整的类型。

  

上述措辞表明,只要我们不声明某个对象属于不完整类型,我们就是明确的;即。我们的模板不会被隐式实例化。

请参阅下面的示例,其中( 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>`