无法使用 Boost.Bimap 解决 g++ 7.1 结构化绑定错误
Failing to work around g++ 7.1 structured binding bug with Boost.Bimap
在我的项目中,我使用 Boost.Bimap 来实现双向地图。
查看 this very simple MCVE on godbolt, where I am using structured binding 以打印右映射的键值对(根据文档,它与 std::map
.
签名兼容
问题
它可以很好地编译任何 g++ 版本 >= 7.4 及更高版本,但是我需要使用 g++ 7.1。此处此代码失败并显示以下消息:
<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) {
我发现 g++ 中的 似乎已在更高版本中修复。
解决方法尝试(玩具示例,成功)
为了使结构化绑定适用于我的编译器版本,我尝试通过特化 std::tuple_size
、std::tuple_element
和 std::get
来创建一个解决方法。有关详细信息,请参阅 this cppreference link。
为了简单起见,我先用玩具结构成功地尝试了这个。这是专业,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
请注意,如果您删除 g++ 7.1 的 #ifdef
,编译将失败并出现与上述相同的错误 (...is not an integral constant expression
)。 (有趣:与 boost::bimap
示例不同,它只能在 g++ 7.4 及更高版本上正常编译,玩具示例已经在 g++ 7.2 上成功)
解决方法尝试(原始示例,未成功)
现在,非常确信我找到了解决方案,我尝试对 boost::bimap
做同样的事情,但我无助地失败了 (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; };
}
这里的 post 错误信息太长了(见 godbolt 输出),但基本上我明白 "my" get
的重载没有被编译器匹配。请注意,出于调试原因,我已将以下行插入到我的代码中,以确保我在我的专业化中实际处理的是正确的类型。
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);
}
我做错了什么吗?还是这个错误对我的变通尝试构成了无法逾越的障碍?
我认为这不是您可以解决的问题。
这是一个较短的复制品:
#include <tuple>
namespace N {
struct X {
template <typename T> void get() { }
};
}
namespace std {
template <> struct tuple_size<N::X> : integral_constant<size_t, 1> { };
template <> struct tuple_element<0, N::X> { using type = int; };
}
namespace N {
template <size_t I> decltype(auto) get(X const&) { return 42; }
}
int main() {
auto [i] = N::X{};
}
这是一个有效的程序。 [dcl.struct.bind]/4 的措辞说,强调我的:
The unqualified-id get is looked up in the scope of E
by class member access lookup ([basic.lookup.classref]), and if that finds at least one declaration that is a function template whose first template parameter is a non-type parameter, the initializer is e.get<i>()
. Otherwise, the initializer is get<i>(e)
, where get is looked up in the associated namespaces ([basic.lookup.argdep]).
N::X
有一个采用类型模板参数的成员函数模板 get()
这一事实应该让我们考虑在 get
上进行 ADL 查找,这应该会找到 non-member N::get
。 gcc 7.4 正确执行此操作,gcc 7.3 抱怨 N::X::get()
不工作。
解决这个问题的唯一方法是以某种方式包装初始化程序。基本上做这样的事情:
auto [i] = wrap(N::X{});
其中 wrap
returns 一些新类型肯定没有名为 get
的成员,以便您可以提供您想要的 non-member。我不确定这里是否有不需要额外包装的解决方案。除了使用 gcc 7.4 :-)
在我的项目中,我使用 Boost.Bimap 来实现双向地图。
查看 this very simple MCVE on godbolt, where I am using structured binding 以打印右映射的键值对(根据文档,它与 std::map
.
问题
它可以很好地编译任何 g++ 版本 >= 7.4 及更高版本,但是我需要使用 g++ 7.1。此处此代码失败并显示以下消息:
<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) {
我发现 g++ 中的
解决方法尝试(玩具示例,成功)
为了使结构化绑定适用于我的编译器版本,我尝试通过特化 std::tuple_size
、std::tuple_element
和 std::get
来创建一个解决方法。有关详细信息,请参阅 this cppreference link。
为了简单起见,我先用玩具结构成功地尝试了这个。这是专业,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
请注意,如果您删除 g++ 7.1 的 #ifdef
,编译将失败并出现与上述相同的错误 (...is not an integral constant expression
)。 (有趣:与 boost::bimap
示例不同,它只能在 g++ 7.4 及更高版本上正常编译,玩具示例已经在 g++ 7.2 上成功)
解决方法尝试(原始示例,未成功)
现在,非常确信我找到了解决方案,我尝试对 boost::bimap
做同样的事情,但我无助地失败了 (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; };
}
这里的 post 错误信息太长了(见 godbolt 输出),但基本上我明白 "my" get
的重载没有被编译器匹配。请注意,出于调试原因,我已将以下行插入到我的代码中,以确保我在我的专业化中实际处理的是正确的类型。
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);
}
我做错了什么吗?还是这个错误对我的变通尝试构成了无法逾越的障碍?
我认为这不是您可以解决的问题。
这是一个较短的复制品:
#include <tuple>
namespace N {
struct X {
template <typename T> void get() { }
};
}
namespace std {
template <> struct tuple_size<N::X> : integral_constant<size_t, 1> { };
template <> struct tuple_element<0, N::X> { using type = int; };
}
namespace N {
template <size_t I> decltype(auto) get(X const&) { return 42; }
}
int main() {
auto [i] = N::X{};
}
这是一个有效的程序。 [dcl.struct.bind]/4 的措辞说,强调我的:
The unqualified-id get is looked up in the scope of
E
by class member access lookup ([basic.lookup.classref]), and if that finds at least one declaration that is a function template whose first template parameter is a non-type parameter, the initializer ise.get<i>()
. Otherwise, the initializer isget<i>(e)
, where get is looked up in the associated namespaces ([basic.lookup.argdep]).
N::X
有一个采用类型模板参数的成员函数模板 get()
这一事实应该让我们考虑在 get
上进行 ADL 查找,这应该会找到 non-member N::get
。 gcc 7.4 正确执行此操作,gcc 7.3 抱怨 N::X::get()
不工作。
解决这个问题的唯一方法是以某种方式包装初始化程序。基本上做这样的事情:
auto [i] = wrap(N::X{});
其中 wrap
returns 一些新类型肯定没有名为 get
的成员,以便您可以提供您想要的 non-member。我不确定这里是否有不需要额外包装的解决方案。除了使用 gcc 7.4 :-)