【问题标题】:Template instantiation behaviour changes when using explicit namespace qualifier?使用显式命名空间限定符时模板实例化行为发生变化?
【发布时间】:2021-12-29 12:53:42
【问题描述】:

我一直在试验一个可组合管道的系统,它涉及一组“阶段”,可以模板化。每个阶段都处理自己的设置、执行和清理,模板推导用于构建管道使用的最小“状态”列表。这需要相当多的样板模板代码,这些代码已经显示出一些明显不协调的行为。尽管实验成功了,但实际上将其滚动到我们的代码库中会由于无效的实例化而导致错误。

花了一些时间来找出玩具(工作)解决方案与更丰富的版本之间的区别,但最终将其缩小到明确的命名空间规范。

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&lt;bind_stage&lt;int&gt;,bind_stage&lt;bool&gt;&gt;,它显然将无效的实例化bind_stage&lt;bool&gt; 作为其类型定义的一部分。哪个应该无法编译。

另一方面,它纯粹是作为一个名称,而不是一个定义。在这种情况下,它可能只是一个前向声明的模板,我们希望它能够工作,只要外部模板中没有任何东西真正具体引用它。

我没想到的是两种不同的行为,这取决于我是否添加了(理论上是多余的)全局命名空间说明符。

我已经在 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
}

X&lt;int&gt; 在定义 Y&lt;X&lt;int&gt;&gt; 时被实例化,或者不是。在非指定范围内使用函数有什么关系?

【问题讨论】:

  • test_binding, bind_stage>();顺便说一句。不知道为什么 2. 语句会编译。应该完全一样,你甚至没有使用命名空间等。
  • 确实如此,但部分意图是尝试查看是否可以使用特定实例化和默认实例化之间的差异来指定一组“绑定”类型,这些类型将在所有阶段持续存在在使用相同“密钥”的管道中。
  • 您可以考虑创建一个问题的最小示例。它现在太复杂了,有很多分散注意力的代码(例如,可变参数模板、模板模板参数等)。考虑以下一个:wandbox.org/permlink/GS8xSEQMQOOoaSt6。它更简单,据我所知,它表现出同样的问题。 (如果您愿意,可以将代码粘贴到更新的问题中。)
  • 这显然归结为非限定名与限定名查找。对于非限定名称查找,似乎Y 本身不需要是完整类型,因此X&lt;int&gt; 不需要是完整类型,因此它不是implicitly instantiated。但是对于限定名称查找,显然 Y 和 X&lt;int&gt; 必须被隐式实例化(为什么?)。我似乎找不到会这样说的标准部分。 Y&lt;X&lt;int&gt;&gt; 似乎不需要隐式实例化 X&lt;int&gt;(因为后者的完整性无关紧要)。
  • 对于它的价值,EDG(如 Visual Studio Intellisense)与所有其他编译器一致:i.stack.imgur.com/DfP7E.png

标签: c++ templates language-lawyer instantiation template-argument-deduction


【解决方案1】:

模板在需要时被实例化。那么为什么当以f(Y&lt;X&lt;int&gt;&gt; {}); 执行非限定调用时,编译器会实例化X&lt;int&gt;,而当对f 的调用被限定为::f(X&lt;Y&lt;int&gt;&gt;{}) 时,编译器不会实例化?

原因是Agument-Dependent name Lookup(ADL)(参见[basic.lookup.argdep])只发生在非限定调用中。

在调用 f(Y&lt;X&lt;int&gt;&gt;{}) 的情况下,编译器必须在 X&lt;int&gt; 的定义中查找友元函数的声明:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-30
    • 2021-01-18
    相关资源
    最近更新 更多