c++templateslanguage-lawyertemplate-meta-programming

What is explanation/fix for compiler's error when sorting types of tuple using decltype, lambdas and std::array?


I needed to sort types in a tuple by alignment, and as a result I just want to get a new tuple type.

Wrote some implementation. But in one case (when using consteval auto sort_tuple(auto tuple)) gcc and clang give error, and in another case (when using most enclosing function as lambda) msvc gives error but in different place for (seemingly) different reason. Code (godbolt):

#include <cstddef>
#include <concepts>
#include <utility>
#include <array>
#include <algorithm>

using Index = std::size_t;
using Alignment = std::size_t;
struct Key {
  Index old;
  Alignment a;
};

template<::Key k, typename T> struct Elem {};
template<typename ...Elems> struct Map : Elems... {};

template<typename ...Ts> struct Tuple {};
struct alignas(2) s_1 {};
struct alignas(2) s_2 {};
struct alignas(2) s_3 {};
struct alignas(2) s_4 {};
using T1 = Tuple<char, short, s_1, s_2, s_3, s_4, int, double>;



#define CLANG_GCC_DONT_WORK
//#define MSVC_DOESNT_WORK_BUT_DIFFERENT_PLACE


#if defined(CLANG_GCC_DONT_WORK)


consteval auto sort_tuple(auto tuple)
{


#elif defined(MSVC_DOESNT_WORK_BUT_DIFFERENT_PLACE)


using Sorted = decltype([]() consteval {
  auto tuple = T1{};

  
#endif


  constexpr auto size = []<typename ...Ts>(::Tuple<Ts...>&){
    return sizeof...(Ts);
  }(tuple);

  constexpr auto unsorted = []<
    typename ...Ts,
    auto ...Is
  >(::Tuple<Ts...>&, std::index_sequence<Is...>&&){
    return std::array<::Key, size>{::Key{Is, alignof(Ts)}...};
  }(tuple, std::make_index_sequence<size>{});

  using Unsorted = decltype([]<typename ...Ts, auto ...Is>(
  ::Tuple<Ts...>&, std::index_sequence<Is...>&&
  ){
    return ::Map<
      ::Elem<::Key{Is, alignof(Ts)}, Ts>...
    >{};
  }(tuple, std::make_index_sequence<size>{}));

  constexpr auto sorted = [](auto unsorted){
    std::sort(
      unsorted.begin(), unsorted.end(),
      [](const auto& lhs, const auto& rhs){
        return lhs.a > rhs.a;
      }
    );
    return unsorted;
  }(unsorted);

  // gcc and clang complaining here
  // (when using CLANG_GCC_DONT_WORK macro),
  // I'm using trick with template argument deduction,
  // and those, it seems, can't deduce
  //
  // also "sorted" std::array passed as template parameter,
  // but when passing it through capture
  // all compilers give error at this place
  using Sorted = decltype([]<auto sorted, auto ...Is>(
    std::index_sequence<Is...>&&
  ){

    return ::Tuple<
      decltype([]<::Key k, typename T>(
        [[maybe_unused]] ::Elem<k, T>&& deduced_by_compiler
      ){
        return T{};
      }.template operator()<sorted[Is]>(Unsorted{}))...
    >{};
  }.template operator()<sorted>(std::make_index_sequence<size>{}));

  return Sorted{};


#if defined(CLANG_GCC_DONT_WORK)


};
using Sorted = decltype(sort_tuple(T1{}));


#elif defined(MSVC_DOESNT_WORK_BUT_DIFFERENT_PLACE)


}());


#endif

int main() {}

So:

  1. Is it a compilers' bug and everything according to standard?
  2. Same question but in case of using captures for "sorted" std::array, as I wrote in comments.

Also if somebody happen to know a working implementation of such sorting in C++20 and below, I would appreciate if you link it in comments or something


Solution

  • Can't say this code 100% standard complaint, but it definitely exposes some bugs from every of these three compilers. Here's links to bug reports with minimized examples created from initial example, if interested:

    1. MSVC
    2. Clang
    3. GCC

    With regard to "fixes", MSVC already works pretty much. And @Jarod42 able to make Clang happy (Godbolt).

    Here's working code for GCC (Godbolt):

    #include <cstddef>
    #include <concepts>
    #include <utility>
    #include <array>
    #include <algorithm>
    
    using Index = std::size_t;
    using Alignment = std::size_t;
    struct Key {
      Index old;
      Alignment a;
    };
    
    template<::Key k, typename T> struct Elem {};
    template<typename ...Elems> struct Map : Elems... {};
    
    template<typename ...Ts> struct Tuple {};
    struct alignas(2) s_1 {};
    struct alignas(2) s_2 {};
    struct alignas(2) s_3 {};
    struct alignas(2) s_4 {};
    using T1 = Tuple<char, short, s_1, s_2, s_3, s_4, int, double>;
    
    
    
    
    template<::Key...> struct Key_seq {};
    template<auto array, auto len>
    using Make_key_seq = decltype([]<auto ...Is>(std::index_sequence<Is...>&&){
      return ::Key_seq<array[Is]...>{};
    }(std::make_index_sequence<len>{}));
    
    
    
    
    template<typename ...Ts_>
    consteval auto sort_tuple(::Tuple<Ts_...>&& tuple)
    {
    
      constexpr auto size = []<typename ...Ts>(::Tuple<Ts...>&){
        return sizeof...(Ts);
      }(tuple);
    
    
      constexpr auto unsorted = []<
        typename ...Ts,
        auto ...Is
      >(::Tuple<Ts...>&, std::index_sequence<Is...>&&){
        return std::array<::Key, size>{::Key{Is, alignof(Ts)}...};
      }(tuple, std::make_index_sequence<size>{});
    
    
      constexpr auto sorted = [](auto unsorted){
        std::sort(
          unsorted.begin(), unsorted.end(),
          [](const auto& lhs, const auto& rhs){
            return lhs.a > rhs.a;
          }
        );
        return unsorted;
      }(unsorted);
    
      // changed this part, that now uses 'Make_key_seq'
      using Sorted = decltype([]<auto ...Keys, auto ...Is>(
        ::Key_seq<Keys...>&&,
        std::index_sequence<Is...>&&
      ){
    
        // needed to move it inside here, otherwise error
        using Unsorted = decltype([]<typename ...Ts>(::Tuple<Ts...>&){
          return ::Map<
            ::Elem<::Key{Is, alignof(Ts)}, Ts>...
          >{};
        }(tuple));
    
        return ::Tuple<
          decltype([]<::Key k, typename T>(
            [[maybe_unused]] ::Elem<k, T>&& deduced_by_comp
          ){
            return T{};
          }.template operator()<Keys>(Unsorted{}))...
        >{};
    
      }(::Make_key_seq<sorted, size>{}, std::make_index_sequence<size>{}));
    
    
      return Sorted{};
    };
    using Sorted = decltype(sort_tuple(T1{}));
    
    using Correct = Tuple<double, int, short, s_1, s_2, s_3, s_4, char>;
    static_assert(std::same_as<Correct, Sorted>);
    
    auto main() -> int;