g ++ c ++ 17 вычет аргумента шаблона класса не работает в очень конкретном случае - PullRequest
0 голосов
/ 23 октября 2018

У меня есть следующий код:

template <class T>
class lit {
public:
    lit(T l) : val(l) {}
    T val;
};

template <class T>
class cat {
public:
    cat(lit<T> const& a, lit<T> const& b) : a(a), b(b) {}
    lit<T> const& a;
    lit<T> const& b;
};

template <class T>
cat<T> operator+(lit<T> const& a, lit<T> const& b) {
    return cat(a, b);
}

int main() {
    auto r1 = cat((lit      ('b')),  lit('d')); // compiles
    auto r2 =     (lit      ('b')) + lit('d') ; // doesn't compile
    auto r3 =      lit      ('b')  + lit('d') ; // compiles
    auto r4 =     (lit      ('b'))            ; // compiles
    auto r5 =     (lit<char>('b')) + lit('d') ; // compiles
}

Это прекрасно компилируется с clang (как я и ожидал), но gcc выдает следующую ошибку:

prog.cc: In function 'int main()':
prog.cc:23:20: error: missing template arguments after 'lit'
     auto r2 =     (lit      ('b')) + lit('d') ; // doesn't compile
                    ^~~
prog.cc:2:7: note: 'template<class T> class lit' declared here
 class lit : public ExpressionBuilder<T> {
       ^~~

Кажется, что нетбыть в состоянии выяснить вывод шаблона класса из конструктора только в одном очень специфическом случае (r2).Я предполагаю, что gcc не прав, но кто-то может объяснить, почему он потерпит неудачу только в этом очень конкретном случае?

Пример здесь: https://wandbox.org/permlink/jQCOhXFFQekS17Y1

Ответы [ 2 ]

0 голосов
/ 25 октября 2018

Это то, что, как я считаю, произошло:

Существует два вида выражений, которые выглядят одинаково, но имеют совершенно разные значения:

(type) + expr
(expr) + expr

Первое - это выражение в стиле C, который преобразует унарное выражение + expr в type;второе - это двоичное выражение, которое выполняет сложение.

Чтобы устранить неоднозначность выражения в форме (something) + expr, GCC сначала предполагает, что something является типом, и выполняет предварительный анализ.Если это удается, тогда все выражение обрабатывается как приведенное выражение;в противном случае something обрабатывается как выражение.

Теперь я думаю, что здесь есть ошибка: во время предварительного анализа GCC ошибочно полагает, что дедукция аргумента шаблона класса (CTAD) не может появиться, поэтому выдает ошибкукогда появляется CTAD.Но на самом деле, даже если предварительный синтаксический анализ определенно потерпит неудачу в этом случае, something все еще может быть действительным выражением в стиле функции, и, следовательно, повторный анализ может быть успешным.

Для cat((lit('b')), lit('d')), lit('b') + lit('d') и (lit('b')), GCC достаточно умен, чтобы понять, что они не могут быть выражением в стиле C, поэтому он не выполняет предварительный анализ.Для (lit<char>('b')) + lit('d') в lit<char>('b') нет CTAD, поэтому все в порядке.

Доказательство вышеприведенного анализа:

Если + изменяется на / (илиВ большинстве операторов, кроме -, * или &), ошибки не возникает, поскольку (something) / expr не может быть допустимым выражением приведения.

Подобная неоднозначность существует в sizeof(something) (может бытьsizeof(type) или sizeof(expr)), и, как и ожидалось, sizeof(lit(0)) вызывает аналогичную ошибку.

0 голосов
/ 23 октября 2018

Это совершенно новая функция в C ++ 17 и, следовательно, совершенно новая в GCC.Наблюдаемый вами паттерн (или его отсутствие) очень похож на ошибку компилятора.То, как он запускается, по-видимому, случайным образом, также соответствует этому шаблону.

Детальное изучение «как» и «почему» - утомительная работа для разработчиков GCC, а не для ответа «Переполнение стека», поскольку он может быть чрезвычайно сложным… Но правильный подход сейчас состоит в том, чтобы вызвать ошибку и посмотреть, что произойдет. (OP сделал это, как ошибка 87709 .)

Подобные примеры уже существуют в Bugzilla .

...