【问题标题】:C++: Template Template Member of a Template Parameter as a Parameter to a Template Class Expecting a Template Template parameterC++:模板参数的模板模板成员作为需要模板模板参数的模板类的参数
【发布时间】:2020-05-28 00:40:15
【问题描述】:

首先,为这个可怕的标题道歉。我正在试验 C++20 is_detected 功能。 is_detected 基本上接受两个模板参数,一个是执行检查的高阶类型,另一个是要检查的类型。我在以下场景中遇到了问题:

#include <stdio.h>
#include <type_traits>

// kind of how std::experimental::is_detected is implemented
template <template <typename> typename Checker, typename T, typename = void>
struct is_detected: std::false_type {};

template <template <typename> typename Checker, typename T>
struct is_detected<Checker, T, std::void_t<Checker<T>>>: std::true_type {};

struct Foo {
    template <typename T>
    using Checker = decltype(std::declval<T>().foo());

    template <typename T>
    static constexpr void call(T &t) {
        t.foo();
    }
};

template <typename T>
using GlobalChecker = decltype(std::declval<T>().foo());

template <typename T>
struct Wrapper {
    template <typename U>
    using LocalChecker = typename T::template Checker<U>;
    //                               ^^^^^^^^ clang and msvc require template keyword
    //                                        gcc doesn't require it

    template <typename U>
    constexpr void conditional_call(U &u) const noexcept {
        if constexpr (
            is_detected<
                typename T::Checker,// !!! COMPILE ERROR !!!
                                    // works for
                                    // GlobalChecker,
                                    // LocalChecker and
                                    // Foo::Checker, though.
                std::decay_t<U>>
                ::value) {
            Foo::call(u);
        }
        else {
            puts("fallback");
        }
    }
};

int main() {
    struct {
        void foo() {
            puts("heyy!");
        }
    } t;

    Wrapper<Foo> w;
    w.conditional_call(t); // heyy! (if foo didn't exist, then fallback)
}

如果我在类范围内使用using LocalChecker = typename T::template Checker&lt;U&gt;;Checker 起别名,则它可以工作;但是,我想了解是否有其他方法不使用using。另外,我需要template 这个using 定义吗?因为 Clang 和 GCC 对此意见不一。

【问题讨论】:

    标签: c++ templates c++20 template-templates


    【解决方案1】:

    从 C++17 开始,在某些只能命名类型的上下文(包括 typename-specifier em> 语法,用于LocalChecker 别名模板。所以 clang 和 MSVC 在没有 template 的情况下拒绝该行是错误的,并且可能尚未实施此更改。

    C++14 [temp.names]/4:

    当成员模板特化的名称出现在 postfix-expression 中的 .-&gt; 之后或 nested-name-specifier 之后em>qualified-id,postfix-expression的对象表达式是依赖于类型的 或 qualified-id 中的 nested-name-specifier 指的是依赖类型,但该名称不是当前实例化 (14.6.2.1) 的成员,成员模板名称必须以关键字template 为前缀。否则,该名称被假定为命名非模板。

    已替换为C++17 [temp.names]/4:

    如果关键字template 出现在template-argument-list 之外,则称它出现在qualified-id 的顶层decltype 说明符。在 declarator-idqualified-id 中或在由 class-head-name 形成的 qualified-id 中em> 或 enum-head-name,关键字template 不应出现在顶层。在 qualified-id 中用作 typename-specifierelaborated-type-specifierusing-declaration 中的名称em> 或 class-or-decltype,出现在顶层的可选关键字 template 将被忽略。在这些上下文中,始终假定&lt; 令牌引入了 template-argument-list。在所有其他上下文中,当命名未知特化([temp.dep.type])成员的模板特化时,成员模板名称应以关键字template 为前缀。

    由于上述规则在命名成员模板特化时只需要关键字template,而不仅仅是成员模板本身,所以看起来很简单T::Checker,除了T::template Checker,应该可以在该部分工作使用is_detected 的示例。 (我们不想要typename,因为我们正在命名别名模板,而不是该模板的特化类型。)但不清楚为什么添加模板应该在编译器执行或不执行时有所不同不需要帮助确定含义。打开的CWG issue 1478是相关的。

    无论如何,编译器似乎更喜欢 template 提示:您的带有 is_detected&lt;T::template Checker,... 的程序在 clang++、g++ 和 msvc 上成功编译:see on godbolt

    【讨论】:

    • 这里的实现并不完全一致:有一个relevant Core issue
    • @DavisHerring 哦,对。我错过了第 4 段的两个版本都在谈论模板专业化,而不是模板名称。 (并且编译器是否需要在模板定义时知道名称是类型还是模板?还有一个要求是,当在其自身范围内的类模板的名称用作模板参数时,命名之间的歧义模板和命名当前实例化类类型取决于相应的模板参数,就像TT&lt;int()&gt; 这样的非类型与类型歧义完全一样!)
    • 由于这种歧义,编译器已经不得不处理未知类型的模板参数的情况(即使在您提到的消歧之后),所以告诉他们没有多大意义。