Skip to content

Commit

Permalink
fix: work around msvc compiler bugs related to NTTP/array refs in par…
Browse files Browse the repository at this point in the history
…am packs

It's unclear what exactly is going on here. But use of a reference to
bounded-array as a template parameter inside a parameter pack cause MSVC
to emite useless compiler errors. On MSVC 19.16 it appears to eliminate
these overloads from consideration without any diagnostic (like SFINAE).
On more recent versions it provides the supremely useless "failed to
specialize function template" without informing about *why* it is
failing to do so.

And some variations of these, applying decltype to NTTPs, even ICEs the
compiler.

Converting `const T (&)[N]` to `array_ref<T, N>` allows us to *not* have
a reference-to-array expression inside the parameter pack and this
appears to stop triggering all compiler bugs discovered thus far.
  • Loading branch information
muggenhor committed Aug 15, 2023
1 parent 42cfcb6 commit 3721b1f
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 55 deletions.
13 changes: 13 additions & 0 deletions include/frozen/bits/basic_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ struct size_integer {
template <std::size_t total_size>
using size_integer_t = typename size_integer<total_size>::type;

// Helper type to work around apparent compiler bugs in MSVC related to having "too complex"
// template parameters involving either NTTP or references-to-array.
template <class T, std::size_t N>
struct array_ref {
using array_type = const T(&) [N];
using pointer = T*;

pointer array;

operator pointer () const noexcept { return array; }
operator array_type() const noexcept { return array; }
};

template <class T, std::size_t N>
class cvector {
public:
Expand Down
16 changes: 8 additions & 8 deletions include/frozen/bits/pic_array.h
Original file line number Diff line number Diff line change
Expand Up @@ -525,23 +525,23 @@ struct pic_array {

// Helpers to preserve arrays in type information, instead of letting them decay to pointers
template <typename T, typename U, std::size_t TN, std::size_t UN>
constexpr std::pair<T (&)[TN], U (&)[UN]> kv_pair(T (& key)[TN], U (& val)[UN]) {
return {key, val};
constexpr auto kv_pair(T (& key)[TN], U (& val)[UN]) {
return std::pair<bits::array_ref<T, TN>, bits::array_ref<U, UN>>{{key}, {val}};
}

template <typename T, typename U, std::size_t N>
constexpr std::pair<T (&)[N], U> kv_pair(T (& key)[N], U val) {
return {key, val};
constexpr auto kv_pair(T (& key)[N], U val) {
return std::pair<bits::array_ref<T, N>, U>{{key}, {val}};
}

template <typename T, typename U, std::size_t N>
constexpr std::pair<T, U (&)[N]> kv_pair(T key, U (& val)[N]) {
return {key, val};
constexpr auto kv_pair(T key, U (& val)[N]) {
return std::pair<T, bits::array_ref<U, N>>{{key}, {val}};
}

template <typename T, typename U>
constexpr std::pair<T, U> kv_pair(T key, U val) {
return {key, val};
constexpr auto kv_pair(T key, U val) {
return std::pair<T, U>{{key}, {val}};
}

} // namespace frozen
Expand Down
91 changes: 72 additions & 19 deletions include/frozen/map.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,55 +361,108 @@ constexpr auto make_map(std::array<std::pair<T, U>, N> const &items, Compare con
return map<T, U, N, Compare>{items, compare};
}

template <typename T, typename U, typename Compare, std::size_t... KNs,
std::enable_if_t<!bits::is_pair<Compare>::value>* = nullptr>
template <
typename T
, typename U
, typename Compare
, std::enable_if_t<
!bits::is_pair<Compare>::value
, std::size_t>... KNs
>
constexpr auto make_map(
Compare const& compare,
std::pair<
bits::element_t<T> const (&)[KNs]
bits::array_ref<const bits::element_t<T>, KNs>
, U> const&... items) {
constexpr const auto key_storage_size = bits::accumulate({KNs...});
using container_type = bits::pic_array<std::pair<const T, U>, sizeof...(KNs), key_storage_size>;
return map<T, U, sizeof...(KNs), Compare, container_type>{container_type{items...}, compare};
using value_type = typename container_type::value_type;
return map<T, U, sizeof...(KNs), Compare, container_type>{
container_type{value_type(T(items.first.array), U(items.second))...},
compare,
};
}

template <typename T, typename U, std::size_t... KNs>
constexpr auto make_map(std::pair<bits::element_t<T> const (&)[KNs], U> const&... items) {
template <
typename T
, typename U
, std::size_t... KNs
>
constexpr auto make_map(
std::pair<
bits::array_ref<const bits::element_t<T>, KNs>
, U> const&... items) {
return make_map<T, U>(std::less<T>{}, items...);
}

template <typename T, typename U, typename Compare, std::size_t... VNs,
std::enable_if_t<!bits::is_pair<Compare>::value>* = nullptr>
template <
typename T
, typename U
, typename Compare
, std::enable_if_t<
!bits::is_pair<Compare>::value
, std::size_t>... VNs
>
constexpr auto make_map(
Compare const& compare,
std::pair<
T
, bits::element_t<U> const (&)[VNs]> const&... items) {
, bits::array_ref<const bits::element_t<U>, VNs>> const&... items) {
constexpr const auto key_storage_size = bits::accumulate({VNs...});
using container_type = bits::pic_array<std::pair<const T, U>, sizeof...(VNs), key_storage_size>;
return map<T, U, sizeof...(VNs), Compare, container_type>{container_type{items...}, compare};
using value_type = typename container_type::value_type;
return map<T, U, sizeof...(VNs), Compare, container_type>{
container_type{value_type(T(items.first), U(items.second.array))...},
compare,
};
}

template <typename T, typename U, std::size_t... KNs>
constexpr auto make_map(std::pair<T, bits::element_t<U> const (&)[KNs]> const&... items) {
template <
typename T
, typename U
, std::size_t... KNs
>
constexpr auto make_map(
std::pair<
T
, bits::array_ref<const bits::element_t<U>, KNs>> const&... items) {
return make_map<T, U>(std::less<T>{}, items...);
}

template <typename T, typename U, typename Compare, std::size_t... KNs, std::size_t... VNs,
std::enable_if_t<!bits::is_pair<Compare>::value>* = nullptr>
template <
typename T
, typename U
, typename Compare
, std::size_t... KNs
, std::enable_if_t<
!bits::is_pair<Compare>::value
, std::size_t>... VNs
>
constexpr auto make_map(
Compare const& compare,
std::pair<
bits::element_t<T> const (&)[KNs]
, bits::element_t<U> const (&)[VNs]> const&... items) {
bits::array_ref<const bits::element_t<T>, KNs>
, bits::array_ref<const bits::element_t<U>, VNs>> const&... items) {
constexpr const auto key_storage_size = bits::accumulate({KNs...});
constexpr const auto val_storage_size = bits::accumulate({VNs...});
using container_type = bits::pic_array<std::pair<const T, U>, sizeof...(KNs), key_storage_size + val_storage_size>;
return map<T, U, sizeof...(KNs), Compare, container_type>{container_type{items...}, compare};
using value_type = typename container_type::value_type;
return map<T, U, sizeof...(KNs), Compare, container_type>{
container_type{value_type(T(items.first.array), U(items.second.array))...},
compare,
};
}

template <typename T, typename U, std::size_t... KNs, std::size_t... VNs>
constexpr auto make_map(std::pair<bits::element_t<T> const (&)[KNs], bits::element_t<U> const (&)[VNs]> const&... items) {
template <
typename T
, typename U
, std::size_t... KNs
, std::size_t... VNs
>
constexpr auto make_map(
std::pair<
bits::array_ref<const bits::element_t<T>, KNs>
, bits::array_ref<const bits::element_t<U>, VNs>> const&... items) {
return make_map<T, U>(std::less<T>{}, items...);
}

Expand Down
124 changes: 96 additions & 28 deletions include/frozen/unordered_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,77 +251,145 @@ constexpr auto make_unordered_map(
return unordered_map<T, U, N, Hasher, Equal>{items, hash, equal};
}

template <typename T, typename U, typename Hasher, typename Equal, std::size_t... KNs,
std::enable_if_t<
!bits::has_type<bits::element_type<U>>::value
&& !bits::is_pair<Hasher>::value
&& !bits::is_pair<Equal>::value>* = nullptr>
template <
typename T
, typename U
, typename Hasher
, typename Equal
, typename ElemT
, std::enable_if_t<
!bits::has_type<bits::element_type<U>>::value
&& !bits::is_pair<Hasher>::value
&& !bits::is_pair<Equal>::value
&& std::is_same<ElemT, bits::element_t<T>>::value
, std::size_t>... KNs
>
constexpr auto make_unordered_map(
Hasher const &hash,
Equal const &equal,
std::pair<
bits::element_t<T> const (&)[KNs]
bits::array_ref<const ElemT, KNs>
, U> const&... items) {
constexpr const auto key_storage_size = bits::accumulate({KNs...});
using container_type = bits::pic_array<std::pair<const T, U>, sizeof...(KNs), key_storage_size>;
return unordered_map<T, U, sizeof...(KNs), Hasher, Equal, container_type>{container_type{items...}, hash, equal};
using value_type = typename container_type::value_type;
return unordered_map<T, U, sizeof...(KNs), Hasher, Equal, container_type>{
container_type{value_type(T(items.first.array), U(items.second))...},
hash,
equal,
};
}

template <typename T, typename U, std::size_t... KNs,
std::enable_if_t<!bits::has_type<bits::element_type<U>>::value>* = nullptr>
template <
typename T
, typename U
, typename ElemT
, std::enable_if_t<
!bits::has_type<bits::element_type<U>>::value
&& std::is_same<ElemT, bits::element_t<T>>::value
, std::size_t>... KNs
>
constexpr auto make_unordered_map(
std::pair<
bits::element_t<T> const (&)[KNs]
bits::array_ref<const ElemT, KNs>
, U> const&... items) {
return make_unordered_map<T, U>(anna<T>{}, std::equal_to<T>{}, items...);
}

template <typename T, typename U, typename Hasher, typename Equal, std::size_t... VNs,
std::enable_if_t<
!bits::has_type<bits::element_type<T>>::value
&& !bits::is_pair<Hasher>::value
&& !bits::is_pair<Equal>::value>* = nullptr>
template <
typename T
, typename U
, typename Hasher
, typename Equal
, typename ElemT
, std::enable_if_t<
!bits::has_type<bits::element_type<T>>::value
&& !bits::is_pair<Hasher>::value
&& !bits::is_pair<Equal>::value
&& std::is_same<ElemT, bits::element_t<U>>::value
, std::size_t>... VNs
>
constexpr auto make_unordered_map(
Hasher const &hash,
Equal const &equal,
std::pair<
T
, bits::element_t<U> const (&)[VNs]
, bits::array_ref<const ElemT, VNs>
> const&... items) {
constexpr const auto val_storage_size = bits::accumulate({VNs...});
using container_type = bits::pic_array<std::pair<const T, U>, sizeof...(VNs), val_storage_size>;
return unordered_map<T, U, sizeof...(VNs), Hasher, Equal, container_type>{container_type{items...}, hash, equal};
using value_type = typename container_type::value_type;
return unordered_map<T, U, sizeof...(VNs), Hasher, Equal, container_type>{
container_type{value_type(T(items.first), U(items.second.array))...},
hash,
equal,
};
}

template <typename T, typename U, std::size_t... VNs,
std::enable_if_t<!bits::has_type<bits::element_type<T>>::value>* = nullptr>
template <
typename T
, typename U
, typename ElemT
, std::enable_if_t<
!bits::has_type<bits::element_type<T>>::value
&& std::is_same<ElemT, bits::element_t<U>>::value
, std::size_t>... VNs
>
constexpr auto make_unordered_map(
std::pair<
T
, bits::element_t<U> const (&)[VNs]
, bits::array_ref<const ElemT, VNs>
> const&... items) {
return make_unordered_map<T, U>(anna<T>{}, std::equal_to<T>{}, items...);
}

template <typename T, typename U, typename Hasher, typename Equal, std::size_t... KNs, std::size_t... VNs,
std::enable_if_t<!bits::is_pair<Hasher>::value && !bits::is_pair<Equal>::value>* = nullptr>
template <
typename T
, typename U
, typename Hasher
, typename Equal
, typename ElemT
, typename ElemU
, std::size_t... KNs
, std::enable_if_t<
!bits::is_pair<Hasher>::value
&& !bits::is_pair<Equal>::value
&& std::is_same<ElemT, bits::element_t<T>>::value
&& std::is_same<ElemU, bits::element_t<U>>::value
, std::size_t>... VNs
>
constexpr auto make_unordered_map(
Hasher const &hash,
Equal const &equal,
std::pair<
bits::element_t<T> const (&)[KNs]
, bits::element_t<U> const (&)[VNs]
bits::array_ref<const ElemT, KNs>
, bits::array_ref<const ElemU, VNs>
> const&... items) {
constexpr const auto key_storage_size = bits::accumulate({KNs...});
constexpr const auto val_storage_size = bits::accumulate({VNs...});
using container_type = bits::pic_array<std::pair<const T, U>, sizeof...(KNs), key_storage_size + val_storage_size>;
return unordered_map<T, U, sizeof...(KNs), Hasher, Equal, container_type>{container_type{items...}, hash, equal};
using value_type = typename container_type::value_type;
return unordered_map<T, U, sizeof...(KNs), Hasher, Equal, container_type>{
container_type{value_type(T(items.first.array), U(items.second.array))...},
hash,
equal,
};
}

template <typename T, typename U, std::size_t... KNs, std::size_t... VNs>
template <
typename T
, typename U
, typename ElemT
, typename ElemU
, std::size_t... KNs
, std::enable_if_t<
std::is_same<ElemT, bits::element_t<T>>::value
&& std::is_same<ElemU, bits::element_t<U>>::value
, std::size_t>... VNs
>
constexpr auto make_unordered_map(std::pair<
bits::element_t<T> const (&)[KNs]
, bits::element_t<U> const (&)[VNs]
bits::array_ref<const ElemT, KNs>
, bits::array_ref<const ElemU, VNs>
> const&... items) {
return make_unordered_map<T, U>(anna<T>{}, std::equal_to<T>{}, items...);
}
Expand Down
20 changes: 20 additions & 0 deletions tests/test_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,3 +499,23 @@ TEST_CASE("frozen::map <> frozen::make_map transparent", "[map]") {
REQUIRE(frozen_empty_map3.begin() == frozen_empty_map3.end());
}
}

TEST_CASE("frozen::make_map variations with frozen::string", "[map]") {
using frozen::kv_pair;

constexpr auto si = frozen::make_map<frozen::string, int>(
kv_pair("a", 1)
, kv_pair("b", 2)
);
constexpr auto is = frozen::make_map<int, frozen::string>(
kv_pair(1, "a")
, kv_pair(2, "b")
);
constexpr auto ss = frozen::make_map<frozen::string, frozen::string>(
kv_pair("1", "a")
, kv_pair("2", "b")
);

static_assert(is.at(si.at("a")) == "a", "");
static_assert(ss.at("1") == "a", "");
}
Loading

0 comments on commit 3721b1f

Please sign in to comment.