LINUX.ORG.RU

Не уверен баг ли в GCC или нет

 , , ,


1

1

Есть код с C++Weekly от Jason Turner (только немного изменены имена): https://wandbox.org/permlink/EWlifMBMhvBhSn5B

Info: http://eel.is/c++draft/temp.variadic#5

template <typename... Functors>
struct CompositeVisitor : public Functors...
{
#if 1
  template <typename... Args>
  CompositeVisitor(Args&&... args) : Functors{std::forward<Args>(args)}...
  {
  }
#endif

  using Functors::operator()...;
};

template <typename... Functors>
CompositeVisitor(Functors...) -> CompositeVisitor<std::decay_t<Functors>...>;

struct Foo {};
struct Bar {};
struct Baz {};

int main() {
  CompositeVisitor visitor{
    [](const auto&) { std::puts("got some part"); return true; },
    [](const Bar&) { std::puts("got Bar"); return false; }
  };
  
  Foo foo;
  Bar bar;
  Baz baz;
  
  visitor(foo);
  visitor(bar);
  visitor(baz);
}

Clang собирает и работает, GCC не хочет:

prog.cc: In instantiation of 'CompositeVisitor<Functors>::CompositeVisitor(Args&& ...) [with Args = {main()::<lambda(const auto:1&)>, main()::<lambda(const Bar&)>}; Functors = {}]':
prog.cc:30:3:   required from here
prog.cc:11:72: error: mismatched argument pack lengths while expanding 'Functors'
   11 |   CompositeVisitor(Args&&... args) : Functors{std::forward<Args>(args)}...
      |                                                                        ^~~

Если подать типы Functors explicit, то собирается. Только с лямбдами немного накладно сделать.

Баг ли?

А почему:

CompositeVisitor(Args&&... args) : Functors{std::forward<Args>(args)}...
а не:
CompositeVisitor(Args&&... args) : Functors{std::forward<Args>(args)...}...
?

В чем вообще смысл конструктора в CompositeVisitor?

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Каждый аргумент конструктора инициализирует один из базовых классов. А это

Functors{std::forward<Args>(args)...}...

инициализурует каждый базовый класс всеми аргументами.

xaizek ★★★★★ ()
Ответ на: комментарий от eao197

А почему:

Потому что идея передать по аргументу каждому функтору, а не все аргументы всем функторам. Таким спомобом можно нормально инициализировать лямбды с захватами.

В чем вообще смысл конструктора в CompositeVisitor?

Без него работает везде, но суть вопроса как раз о нем.

KennyMinigun ★★★★★ ()
Ответ на: комментарий от anonymous

Ага, то есть там две проблемы. Первая описана по ссылке, но даже если заменить инициализатор на Functors{std::forward<Args>(args)...}... (под GCC оно ошибочно работает как оригинальный вариант)

Все равно остается проблема дедукции типов: https://wandbox.org/permlink/QIpMcJHL0iZkhMIM

prog.cc: In function 'int main()':
prog.cc:36:14: error: no match for call to '(CompositeVisitor<>) (Foo&)'
   36 |   visitor(foo);
      |              ^
prog.cc:37:14: error: no match for call to '(CompositeVisitor<>) (Bar&)'
   37 |   visitor(bar);
      |              ^
prog.cc:38:14: error: no match for call to '(CompositeVisitor<>) (Baz&)'
   38 |   visitor(baz);
      |  
KennyMinigun ★★★★★ ()
Последнее исправление: KennyMinigun (всего исправлений: 1)
Ответ на: комментарий от KennyMinigun

Там вроде баг в том, что при пустом списке параметров не предпочитается deduction guide. Так что не совсем оно.

Но так или иначе вокэраунд подходит и тут.

anonymous ()
Ответ на: комментарий от anonymous

Из любопытства: ты в продакшн так пишешь,

Ну… почти. Я в Research сейчас работаю.

или вся эта магия и копания в стандарте - хобби?

Найди себе работу как хобби и никогда не будешь работать. (или как-то так)

KennyMinigun ★★★★★ ()

А чем плохо вместо deduction guide просто сделать функцию?

template <typename... Functors>
constexpr auto make_visitor(Functors&&... f) {
    return CompositeVisitor<Functors...>{std::forward<Functors>(f)...};
}

int main() {
  CompositeVisitor visitor = make_visitor(
    [](const auto&) { std::puts("got some part"); return true; },
    [](const Bar&) { std::puts("got Bar"); return false; }
  );

Вроде работает и на шланге и на gcc.

pftBest ★★★★ ()
Ответ на: комментарий от KennyMinigun

Я уже написал тебе проблему, но объясню ещё раз.

под GCC оно ошибочно работает как оригинальный вариант

В твоих фантазиях оно работает. Но давай я тебе расскажу.

Functors{std::forward<Args>(args)}...

В этом выражении длинна Functors и Args/args связывается. Это значит, что если Functors будет нулевой, а Args не нулевой - у тебя напишет:

mismatched argument pack lengths while expanding

Тебе и написало.

Когда же ты пишешь:

Functors{std::forward<Args>(args)...}...
Ты отвязываешь одно от другого, по-сути говоря «инициализируй мне СКОЛЬКО УГОДНО типов Functors... аргументами std::forward<Args>(args)... . И тут очевидно, что если Functors нулевой, то код должен работать и работает правильно.

Таким образом можно найти ошибку - gcc выводит CompositeVisitor<>, т.е. нулевой Functors.

Почему так может происходить? Потому что CompositeVisitor<> генерирует валидный код и получается расхождение. Т.е. гцц нашел решение подходящее под:

CompositeVisitor visitor{
    [](const auto&) { std::puts("got some part"); return true; },
    [](const Bar&) { std::puts("got Bar"); return false; }
  };

И оно действительно подходит - CompositeVisitor<> - вызвает шаблонный конструктор и передаёт туда аргументы. Далее уже на этапе распаковки происходит ошибка.

И тут возникает вопрос - позволяет ли стандарт подобное поведение, либо нет. Можешь спросить у кого или погуглить.

anonymous ()
Ответ на: комментарий от pftBest

Да, оно работает. Но вопрос изначально не о том.

А чем плохо вместо deduction guide просто сделать функцию?

Template deduction guidelines как раз позволяют не писать функцию. Меньше сущностей.

KennyMinigun ★★★★★ ()
Ответ на: комментарий от anonymous

Почему так может происходить? Потому что CompositeVisitor<> генерирует валидный код и получается расхождение.

По-твоему, CompositeVisitor<> и CompositeVisitor это одно и то же?

anonymous ()