The following class will trigger a static assert error within std::map due to differing value_type
s 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