Search code examples
c++templatesc++20template-templates

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


First of all, apologies for the horrible title. I was experimenting with the C++20 is_detected functionality. is_detected basically takes two template parameters, one is a higher-order type which performs checking and the other one is the type to be checked. I ran into problems in the following scenario:

#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)
}

If I alias the Checker in the class scope using the using LocalChecker = typename T::template Checker<U>;, it works; however, I want to learn if there is another way without using using. In addition, do I need the template in this using definition? Because Clang and GCC disagree on that.


Solution

  • Starting with C++17, you do not need the template keyword between a :: and a member template specialization name in certain contexts that can only name a type, including the typename-specifier syntax, which is used in the LocalChecker alias template. So clang and MSVC are wrong to reject that line without the template, and possibly haven't yet implemented this change.

    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.

    was replaced with 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.

    Since the rules above only require the keyword template when a member template specialization is named, not just the member template itself, it would seem plain T::Checker, in addition to T::template Checker, ought to work at the part of the example using is_detected. (We don't want typename, since we're naming the alias template, not a type which is a specialization of that template.) But it's not clear why adding a template should make a difference in when a compiler does or doesn't need help determining the meaning. The open CWG issue 1478 is related.

    In any case, the compilers do seem to like it better with the template hint: your program with is_detected<T::template Checker,... compiles successfully on clang++, g++, and msvc: see on godbolt.