无法解决 Boost.Bimap 的 g++ 7.1 结构化绑定错误

Failing to work around g++ 7.1 structured binding bug with Boost.Bimap

提问人:andreee 提问时间:5/7/2019 最后编辑:andreee 更新时间:5/13/2019 访问量:186

问:

在我的项目中,我使用 Boost.Bimap 来实现双向地图。

看看 godbolt 上这个非常简单的 MCVE,我在其中使用结构化绑定来打印正确映射的键值对(根据文档,它与 .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++ 中的一个错误,该错误似乎已在以后的版本中得到修复。

变通方法尝试(玩具示例,成功)

为了使结构化绑定适用于我的编译器版本,我试图通过专门化 、 和 来创建一种解决方法。有关详细信息,请参阅此 cppreference 链接std::tuple_sizestd::tuple_elementstd::get

为了简单起见,我首先成功地用玩具结构尝试了这一点。以下是专业化,请查看 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

请注意,如果删除 for g++ 7.1.,编译将失败,并出现与上述相同的错误 ()。(有趣的是:与 boost::bimap 示例不同,它只能在 g++ 7.4 及更高版本中正常编译,而 toy 示例在 g++ 7.2 中已经成功了)#ifdef...is not an integral constant expression

变通办法尝试(原始示例,未成功)

现在,我非常确信我找到了解决方案,我尝试做同样的事情,但我无助地失败了(在 godbolt.org 上查看):boost::bimap

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; };
}

错误消息太长,无法在此处发布(参见 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);
}

我做错了什么吗?还是这个错误对我的解决方法尝试构成了不可逾越的障碍?

C G++ C++17 结构化绑定 boost-bimap

评论


答:

1赞 Barry 5/8/2019 #1

我不认为这是你可以解决的问题。

这是一个较短的复制品:

#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 的措辞说,强调我的:

unqualified-id get 在 by 类成员访问查找 ([basic.lookup.classref]) 的范围内查找,如果找到至少一个声明,该声明是函数模板,其第一个模板参数是非类型参数,则初始值设定项为 。否则,初始值设定项为 ,其中 get 在关联的命名空间 ([basic.lookup.argdep]) 中查找。Ee.get<i>()get<i>(e)

事实上,有一个成员函数模板采用类型模板参数,这应该导致我们考虑 ADL 查找 ,它应该找到非成员 。GCC 7.4 正确地执行此操作,GCC 7.3 抱怨无法正常工作。N::Xget()getN::getN::X::get()


解决此问题的唯一方法是以某种方式包装初始值设定项。基本上可以做这样的事情:

auto [i] = wrap(N::X{});

where 返回一些肯定没有名为 的成员的新类型,以便您可以提供所需的非成员。我不确定这里是否有不需要额外包装的解决方案。除了使用 gcc 7.4 :-)wrapget

评论

0赞 andreee 5/8/2019
很有意思。似乎这种情况的 ADL 查找在 gcc 7.1 中完全中断了。 甚至不需要是函数模板,无法进行类似的编译。事实上,任何带有 name get 的定义都会导致编译错误...N::X::get()void get() { }
0赞 Barry 5/8/2019
@andreee 不是 ADL 坏了,而是第一次查找 - 如果 ADL 失败,我们应该退出并尝试 ADL,但我们没有。