llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.92k stars 11.53k forks source link

Template lambda mixing up types in some template contexts #86606

Open joshuamaiche opened 5 months ago

joshuamaiche commented 5 months ago

Apologies for the slightly convoluted repro; the behavior seems to manifest when only certain templates are at play.

main.cpp

#include <type_traits>

template <typename... Ts>
struct TypeList
{
    template <typename T>
    static constexpr bool Contains()
    {
        return (std::is_same_v<T, Ts> || ...);
    }

    template <auto checker>
    using evaluate_on_all_t =
        std::integral_constant<bool,
            (checker.template operator()<Ts>() || ...)>;

    // Works fine
    template <typename T>
    static constexpr bool overlaps_v =
        evaluate_on_all_t<
            []<typename U>(){return T::template Contains<U>();}
        >::value;

    // Fails to compile
    template <typename T>
    using overlaps_t =
        evaluate_on_all_t<
            []<typename U>(){return T::template Contains<U>();}
        >;
};

using MyTypeList = TypeList<float, int>;
constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine
using TypeListOverlap_t = MyTypeList::overlaps_t<TypeList<int, bool>>; // Fails to compile

int main()
{}

The relevant parts here are overlaps_v and overlaps_t. Both use a template lambda to evaluate if a TypeList T contains any relevant types. In the case of overlaps_v, a template constexpr variable, T is correctly interpreted as being a TypeList, and compiles. However, in the case of overlaps_t, a template alias, the compiler seems to get confused, and thinks T refers to the individual variadic types of the TypeList. Note that the lambdas are identical; the main difference seems to be whether a constexpr variable or an alias is being used.

Console

>clang++ -std=c++20 main.cpp
main.cpp:28:37: error: type 'float' cannot be used prior to '::' because it has no members
            []<typename U>(){return T::template Contains<U>();}
                                    ^
main.cpp:15:31: note: in instantiation of function template specialization 'TypeList<float, int>::(anonymous
      class)::operator()<float>' requested here
            (checker.template operator()<Ts>() || ...)>;
                              ^
main.cpp:27:9: note: in instantiation of template type alias 'evaluate_on_all_t' requested here
        evaluate_on_all_t<
        ^
main.cpp:33:36: note: in instantiation of template class 'TypeList<float, int>' requested here
constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine
                                   ^
main.cpp:28:37: error: type 'int' cannot be used prior to '::' because it has no members
            []<typename U>(){return T::template Contains<U>();}
                                    ^
main.cpp:15:31: note: in instantiation of function template specialization 'TypeList<float, int>::(anonymous
      class)::operator()<int>' requested here
            (checker.template operator()<Ts>() || ...)>;
                              ^
main.cpp:27:9: note: in instantiation of template type alias 'evaluate_on_all_t' requested here
        evaluate_on_all_t<
        ^
main.cpp:33:36: note: in instantiation of template class 'TypeList<float, int>' requested here
constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine
                                   ^
main.cpp:28:37: error: type 'int' cannot be used prior to '::' because it has no members
            []<typename U>(){return T::template Contains<U>();}
                                    ^
main.cpp:15:31: note: in instantiation of function template specialization 'TypeList<int, bool>::(anonymous
      class)::operator()<int>' requested here
            (checker.template operator()<Ts>() || ...)>;
                              ^
main.cpp:27:9: note: in instantiation of template type alias 'evaluate_on_all_t' requested here
        evaluate_on_all_t<
        ^
main.cpp:21:37: note: in instantiation of template class 'TypeList<int, bool>' requested here
            []<typename U>(){return T::template Contains<U>();}
                                    ^
main.cpp:33:48: note: in instantiation of static data member 'TypeList<float, int>::overlaps_v<TypeList<int, bool>>'
      requested here
constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine
                                               ^
main.cpp:28:37: error: type 'bool' cannot be used prior to '::' because it has no members
            []<typename U>(){return T::template Contains<U>();}
                                    ^
main.cpp:15:31: note: in instantiation of function template specialization 'TypeList<int, bool>::(anonymous
      class)::operator()<bool>' requested here
            (checker.template operator()<Ts>() || ...)>;
                              ^
main.cpp:27:9: note: in instantiation of template type alias 'evaluate_on_all_t' requested here
        evaluate_on_all_t<
        ^
main.cpp:21:37: note: in instantiation of template class 'TypeList<int, bool>' requested here
            []<typename U>(){return T::template Contains<U>();}
                                    ^
main.cpp:33:48: note: in instantiation of static data member 'TypeList<float, int>::overlaps_v<TypeList<int, bool>>'
      requested here
constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine
                                               ^
4 errors generated.

version

>clang++ --version
clang version 16.0.5
Target: i686-pc-windows-msvc
Thread model: posix
llvmbot commented 5 months ago

@llvm/issue-subscribers-clang-frontend

Author: None (joshuamaiche)

Apologies for the slightly convoluted repro; the behavior seems to manifest when only certain templates are at play. **main.cpp** ``` #include <type_traits> template <typename... Ts> struct TypeList { template <typename T> static constexpr bool Contains() { return (std::is_same_v<T, Ts> || ...); } template <auto checker> using evaluate_on_all_t = std::integral_constant<bool, (checker.template operator()<Ts>() || ...)>; // Works fine template <typename T> static constexpr bool overlaps_v = evaluate_on_all_t< []<typename U>(){return T::template Contains<U>();} >::value; // Fails to compile template <typename T> using overlaps_t = evaluate_on_all_t< []<typename U>(){return T::template Contains<U>();} >; }; using MyTypeList = TypeList<float, int>; constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine using TypeListOverlap_t = MyTypeList::overlaps_t<TypeList<int, bool>>; // Fails to compile int main() {} ``` The relevant parts here are `overlaps_v` and `overlaps_t`. Both use a template lambda to evaluate if a TypeList `T` contains any relevant types. In the case of `overlaps_v`, a template constexpr variable, `T` is correctly interpreted as being a TypeList, and compiles. However, in the case of `overlaps_t`, a template alias, the compiler seems to get confused, and thinks `T` refers to the individual variadic types of the TypeList. Note that the lambdas are identical; the main difference seems to be whether a constexpr variable or an alias is being used. **Console** ``` >clang++ -std=c++20 main.cpp main.cpp:28:37: error: type 'float' cannot be used prior to '::' because it has no members []<typename U>(){return T::template Contains<U>();} ^ main.cpp:15:31: note: in instantiation of function template specialization 'TypeList<float, int>::(anonymous class)::operator()<float>' requested here (checker.template operator()<Ts>() || ...)>; ^ main.cpp:27:9: note: in instantiation of template type alias 'evaluate_on_all_t' requested here evaluate_on_all_t< ^ main.cpp:33:36: note: in instantiation of template class 'TypeList<float, int>' requested here constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine ^ main.cpp:28:37: error: type 'int' cannot be used prior to '::' because it has no members []<typename U>(){return T::template Contains<U>();} ^ main.cpp:15:31: note: in instantiation of function template specialization 'TypeList<float, int>::(anonymous class)::operator()<int>' requested here (checker.template operator()<Ts>() || ...)>; ^ main.cpp:27:9: note: in instantiation of template type alias 'evaluate_on_all_t' requested here evaluate_on_all_t< ^ main.cpp:33:36: note: in instantiation of template class 'TypeList<float, int>' requested here constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine ^ main.cpp:28:37: error: type 'int' cannot be used prior to '::' because it has no members []<typename U>(){return T::template Contains<U>();} ^ main.cpp:15:31: note: in instantiation of function template specialization 'TypeList<int, bool>::(anonymous class)::operator()<int>' requested here (checker.template operator()<Ts>() || ...)>; ^ main.cpp:27:9: note: in instantiation of template type alias 'evaluate_on_all_t' requested here evaluate_on_all_t< ^ main.cpp:21:37: note: in instantiation of template class 'TypeList<int, bool>' requested here []<typename U>(){return T::template Contains<U>();} ^ main.cpp:33:48: note: in instantiation of static data member 'TypeList<float, int>::overlaps_v<TypeList<int, bool>>' requested here constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine ^ main.cpp:28:37: error: type 'bool' cannot be used prior to '::' because it has no members []<typename U>(){return T::template Contains<U>();} ^ main.cpp:15:31: note: in instantiation of function template specialization 'TypeList<int, bool>::(anonymous class)::operator()<bool>' requested here (checker.template operator()<Ts>() || ...)>; ^ main.cpp:27:9: note: in instantiation of template type alias 'evaluate_on_all_t' requested here evaluate_on_all_t< ^ main.cpp:21:37: note: in instantiation of template class 'TypeList<int, bool>' requested here []<typename U>(){return T::template Contains<U>();} ^ main.cpp:33:48: note: in instantiation of static data member 'TypeList<float, int>::overlaps_v<TypeList<int, bool>>' requested here constexpr bool TypeListOverlap_v = MyTypeList::overlaps_v<TypeList<int, bool>>; // Works fine ^ 4 errors generated. ``` **version** ``` >clang++ --version clang version 16.0.5 Target: i686-pc-windows-msvc Thread model: posix ```