使用显式命名空间限定符时,模板实例化行为会发生变化吗?
我一直在试验一个用于可组合管道的系统,它涉及一组可能是模板化的"阶段"。每个阶段处理自己的设置、执行和清理,模板演绎用于构建流水线使用的"状态"的最小列表。这需要相当多的样板模板代码,这已经显示出一些明显不一致的行为。尽管实验成功,但由于无效的实例化,实际将其放入我们的代码库中会导致错误。
花了一些时间来找出玩具(工作的)解决方案和更丰富的版本之间的差异,但最终缩小到显式命名空间规范。
template<typename KeyType = bool>
struct bind_stage
{
static_assert(!std::is_same<KeyType, bool>::value, "Nope, someone default instantiated me");
};
template<typename BoundStage, typename DefaultStage>
struct test_binding {};
template<template<typename...>class StageTemplate, typename S, typename T>
struct test_binding <StageTemplate<S>, StageTemplate<T>> {};
template<typename T>
auto empty_function(T b) {}
然后我们的Main:
int main()
{
auto binder = test_binding<bind_stage<int>, bind_stage<>>();
//empty_function(binder); // Fails to compile
::empty_function(binder); // Compiles happily
return 0;
}
现在,我不确定我是否预料到了失败。一方面,我们创建了一个test_binder<bind_stage<int>,bind_stage<bool>>
,它显然将无效的实例化bind_stage<bool>
作为其类型定义的一部分。它应该无法编译。
另一方面,它纯粹是作为一个名称包含在内,而不是一个定义。在这种情况下,它可能只是一个转发声明的模板,只要外部模板中没有具体引用它,我们就会期望它工作。
我没有预料到的是,根据我是否添加了(理论上多余的)全局命名空间说明符,会出现两种不同的行为。
我已经在Visual Studio、Clang和GCC中尝试过此代码。它们都有相同的行为,这让我远离这是一个编译器错误。此行为是否由C++标准中的某些内容解释?
编辑: 丹尼尔・兰格的另一个例子,对我来说意义不大:
template <typename T>
struct X {
static_assert(sizeof(T) == 1, "Why doesn't this happen in both cases?");
};
template <typename T>
struct Y { };
template <typename T>
void f(T) { }
int main() {
auto y = Y<X<int>>{};
// f(y); // triggers static assertion
::f(y); // does not
}
定义Y<X<int>>
时实例化了X<int>
,或者没有实例化。在非指定作用域中使用函数与任何事情有什么关系?
解决方案
模板在需要时实例化。那么,为什么在像f(Y<X<int>> {});
那样执行非限定调用时,编译器会实例化X<int>
,而在::f(X<Y<int>>{})
中对f
的调用不实例化呢?
原因是依赖于名称的名称查找(ADL)(请参阅[basic.lookup.argdep]),它仅适用于非限定呼叫。
对于调用f(Y<X<int>>{})
,编译器必须在X<int>
的定义中查找友元函数的声明:
template <typename T>
struct X {
//such function will participate to the overload resolution
//to determine which function f is called in "f(Y<X<int>>{})"
friend void f(X&){}
};
ADL涉及专门化的模板参数类型,即函数参数的类型(哎呀...)是如此不受欢迎(因为它几乎只会带来糟糕的惊喜),以至于有人提议移除它:P0934
相关文章