我一直在试验可组合管道系统,该系统涉及一组可以模板化的“阶段”。每个阶段处理自己的设置、执行和清理,并且模板推导用于构建管道使用的最小“状态”列表。这需要相当多的样板模板代码,这已经显示出一些明显不协调的行为。尽管实验成功,但实际上将其滚动到我们的代码库中会因无效实例化而导致错误。
我们花了一些时间来追踪玩具(工作)解决方案和更丰富的版本之间的差异,但最终它被缩小到一个显式的命名空间规范。
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) {}
然后我们的主要:
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++ 标准中的某些内容是否解释了此行为?
编辑:
Daniel Langr 的另一个例子对我来说意义不大:
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
}
Either X<int>
在定义时实例化Y<X<int>>
或者它不是。在非指定范围内使用函数有什么关系?