Search code examples
c++templateslambdac++20stdmap

static assertion failed with unordered map when using non-type template and empty lambda


The following class will trigger a static assert error within std::map due to differing value_types of the allocator, despite the allocator being defaulted:

template < bool store>
class MyClass {
  public:
   using hashmap_type = std::unordered_map< 
        int, 
        decltype([](){})
    >;

  private:
   std::map< int, hashmap_type > m_mapofmaps;
};

using it via Myclass<false> v{}; triggers

/opt/compiler-explorer/gcc-11.1.0/include/c++/11.1.0/bits/hashtable.h:191:21: error: static assertion failed: unordered container must have the same value_type as its allocator
  191 |       static_assert(is_same<typename _Alloc::value_type, _Value>{},

as can be seen here (GCC 11.1):

But if the non-type template store is removed the compiler is suddenly having no issues anymore. The same holds for exchanging the lambda [](){} with another type, see here and here.

What is this odd interplay between the boolean non-type template, the lambda, and allocators?


Solution

  • Seems to be a bug with how gcc substitutes default arguments to template parameters involving unevaluated lambda types.

    A similar example:

    #include <type_traits>
    
    template<typename T, typename U = T>
    struct assert_is_same {
        static_assert(std::is_same_v<T, U>);
    };
    
    template<bool B>
    struct X {
        assert_is_same<decltype([](){})> x;
    };
    
    int main() {
        X<false> a;
    }
    

    Without the template<bool B>, decltype([](){}) is not a dependent expression, so is evaluated more eagerly (and you end up with something like assert_is_same<_ZN1XILb0EEUlvE0_E, _ZN1XILb0EEUlvE0_E>)

    Inside the template, [](){}'s type is now dependent on B, so can't be replaced with a type immediately. It seems like gcc expands it to assert_is_same<decltype([](){}), decltype([](){})>, where those two lambdas are now different. (the default argument in your map example is std::allocator<std::pair<const Key, T>>, or std::allocator<std::pair<const int, decltype([](){})>>)

    A workaround is to give the lambda something named it can hide behind:

    private:
        static constexpr auto lambda = [](){};
    public:
        using hashmap_type = std::unordered_map< 
            int, 
            decltype(lambda)
        >;
        // allocator is now `std::pair<const int, decltype(lambda)>`, and
        // the two `decltype(lambda)`'s have the same type