C++:模板参数的模板模板成员作为模板的参数 Class 需要模板模板参数

C++: Template Template Member of a Template Parameter as a Parameter to a Template Class Expecting a Template Template parameter

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

#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<U>; 在 class 范围内为 Checker 添加别名,它就可以工作;但是,我想了解是否有另一种不使用 using 的方法。另外,这个using定义里需要template吗?因为 Clang 和 GCC 不同意这一点。

从 C++17 开始,在某些只能命名类型的上下文中,您不需要 :: 和成员模板专业化名称之间的 template 关键字,包括 typename-specifier 语法,用于 LocalChecker 别名模板。因此 clang 和 MSVC 在没有 template 的情况下拒绝该行是错误的,并且可能尚未实施此更改。

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

When the name of a member template specialization appears after . or -> in a postfix-expression or after a nested-name-specifier in a qualified-id, and the object expression of the postfix-expression is type-dependent or the nested-name-specifier in the qualified-id refers to a dependent type, but the name is not a member of the current instantiation (14.6.2.1), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.

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

The keyword template is said to appear at the top level in a qualified-id if it appears outside of a template-argument-list or decltype-specifier. In a qualified-id of a declarator-id or in a qualified-id formed by a class-head-name or enum-head-name, the keyword template shall not appear at the top level. In a qualified-id used as the name in a typename-specifier, elaborated-type-specifier, using-declaration, or class-or-decltype, an optional keyword template appearing at the top level is ignored. In these contexts, a < token is always assumed to introduce a template-argument-list. In all other contexts, when naming a template specialization of a member of an unknown specialization ([temp.dep.type]), the member template name shall be prefixed by the keyword template.

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

无论如何,编译器似乎确实更喜欢 template 提示:你的程序 is_detected<T::template Checker,... 在 clang++、g++ 和 msvc 上成功编译:see on godbolt.