In my project I am using Boost.Bimap to implement bidirectional maps.
Look at this very simple MCVE on godbolt, where I am using structured binding to print the key-value pair of the right map (which, according to the documentation, is signature-compatible to std::map
.
Problem
It compiles fine for any g++ version >= 7.4 and later, however I need to use g++ 7.1. and here this code fails with the following message:
<source>: In function 'int main()':
<source>:11:20: error: 'std::tuple_size<const boost::bimaps::relation::structured_pair<boost::bimaps::tags::tagged<const long unsigned int, boost::bimaps::relation::member_at::right>, boost::bimaps::tags::tagged<const std::__cxx11::basic_string<char>, boost::bimaps::relation::member_at::left>, mpl_::na, boost::bimaps::relation::mirror_layout>>::value' is not an integral constant expression
for (const auto& [key, value] : bm.right) {
I was able to find out that this is due to a bug in g++ that seems to have been fixed in later versions.
Workaround attempt (toy example, successful)
In order to make the structured bindings work with my compiler version, I attempted to create a workaround by specializing std::tuple_size
, std::tuple_element
and std::get
. See this cppreference link for more information.
For simplicity, I successfully tried this first with a toy structure. Here are the specializations, check out the full code on godbolt.org:
struct SampleType {
int a = 42;
std::string b = "foo"s;
double c = 3.141;
};
#if (__GNUC__ == 7) && (__GNUC_MINOR__ == 1)
template <std::size_t N>
decltype(auto) get(const ::SampleType& t) {
if constexpr (N==0) return t.a;
else if constexpr (N==1) return t.b;
else return t.c;
}
namespace std {
// Tuple size is 3
template <> struct tuple_size<::SampleType> : std::integral_constant<std::size_t, 3> {};
// Define tuple types
template <std::size_t N> struct tuple_element<N, ::SampleType> {
// Deduce type from get() function template defined above
using type = decltype(::get<N>(std::declval<::SampleType>()));
};
}
#endif
Note that if you remove the #ifdef
for g++ 7.1., the compilation will fail with the same error as above (...is not an integral constant expression
). (Interesting: Unlike the boost::bimap
example, which only compiles fine with g++ 7.4 onwards, the toy example already succeeds with g++ 7.2)
Workaround attempt (original example, not successful)
Now, being very convinced that I found the solution, I made an attempt to do the same for boost::bimap
but I am failing helplessly (check it out on godbolt.org):
template <std::size_t N>
decltype(auto) get(const bimap::right_map::value_type& bm) {
if constexpr (N==0) return bm.first;
else if constexpr (N==1) return bm.second;
}
namespace std {
// Tuple size is 2 -> key-value pair
template <> struct tuple_size<bimap::right_map::value_type> : std::integral_constant<std::size_t, 2> {};
// Define tuple types
template <> struct tuple_element<0, bimap::right_map::value_type> { using type = std::string; };
template <> struct tuple_element<1, bimap::right_map::value_type> { using type = std::size_t; };
}
The error message is too long to post here (see godbolt output), but basically I understand that the overload for "my" get
is not being matched by the compiler. Note that for debugging reasons I have inserted the following line into my code to make sure that I am actually dealing with the correct type in my specializations.
for (const auto& pair : bm.right) {
// Make sure we capture the right type in the specializations above
static_assert(std::is_same<
decltype(pair),
const bimap::right_map::value_type&
>::value);
}
Am I doing something wrong? Or is this bug posing an insurmountable obstactle to my workaround attempt?