Не удалось обойти ошибку структурированной привязки g++ 7.1 с помощью Boost.Bimap

В моем проекте я использую Boost.Bimap для реализации двунаправленных карт.

Посмотрите на этот очень простой MCVE на godbolt, где я использую структурированная привязка для печати пары ключ-значение правой карты (которая, согласно документации, совместима с подписью до 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. См. эту ссылку cppreference для получения дополнительной информации.

Для простоты я успешно попробовал это сначала с игрушечной конструкцией. Вот специализации, ознакомьтесь с полным кодом на 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

Обратите внимание, что если вы удалите #ifdef для g++ 7.1., компиляция завершится с той же ошибкой, что и выше (...is not an integral constant expression). (Интересно: в отличие от примера boost::bimap, который отлично компилируется только с g++ 7.4 и выше, игрушечный пример уже успешно работает с g++ 7.2)

Попытка обходного пути (исходный пример, не успешный)

Теперь, будучи очень уверенным, что нашел решение, я попытался сделать то же самое для boost::bimap, но безуспешно (посмотрите на 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; };
}

Сообщение об ошибке слишком длинное, чтобы публиковать его здесь (см. вывод godbolt), но в основном я понимаю, что компилятор не сопоставляет перегрузку для «моего» 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);
}

Я делаю что-то неправильно? Или эта ошибка создает непреодолимое препятствие для моей попытки обходного пути?


person andreee    schedule 07.05.2019    source источник


Ответы (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 гласит: , выделение мое:

Получение неквалифицированного идентификатора просматривается в области E с помощью поиска доступа к члену класса ([basic.lookup.classref]), и если это находит хотя бы одно объявление, которое является шаблоном функции, чей первый параметр шаблона является нетиповой параметр, инициализатором является e.get<i>(). В противном случае инициализатором является get<i>(e), где get ищется в связанных пространствах имен ([basic.lookup.argdep]).

Тот факт, что N::X имеет шаблон функции-члена get(), который принимает параметр шаблона типа, должен заставить нас затем рассмотреть поиск ADL для get, который должен найти N::get, не являющийся членом. gcc 7.4 делает это правильно, gcc 7.3 жалуется на то, что N::X::get() не работает.


Единственный способ обойти это - как-то обернуть инициализатор. В основном сделайте что-то вроде:

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

Где wrap возвращает какой-то новый тип, который определенно не имеет члена с именем get, так что вы можете указать желаемого нечлена. Я не уверен, что здесь есть решение, которое не требует дополнительной упаковки. Помимо использования gcc 7.4 :-)

person Barry    schedule 07.05.2019
comment
Очень интересно. Похоже, что поиск ADL для этого случая полностью не работает в gcc 7.1. N::X::get() даже не обязательно должен быть шаблоном функции, void get() { } не компилируется аналогичным образом. Фактически, любое определение с именем get приводит к ошибке компиляции.... - person andreee; 08.05.2019
comment
@andreee Это не ADL сломан, это первый поиск - мы должны отступить и попробовать ADL, если он не работает, но мы этого не делаем. - person Barry; 08.05.2019