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

Time:03-08

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?

CodePudding user response:

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
  • Related