我正在研究一个简单的解决方案,解决常见的“有条件的错误类型”问题(如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>;
因此,我想到的解决方案是以这种方式包装条件的两个目标,并实例化条件的结果,因此只有选定的模板才会被实例化。为此,我修改了lazy
和impl::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错误?
编辑: Here是在ideone上运行的SSCCE。它使用GCC 4.8.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>`