Странные ошибки компиляции шаблона: это ошибка g ++, ошибка Clang или ...? - PullRequest
2 голосов
/ 05 марта 2020

Я столкнулся с ошибкой компиляции в некотором сложном коде шаблона C ++, который я упростил следующим образом:


struct MyOptions
{
    static const size_t maxArray = 2;
    static const uint maxIdx = 8;
};

class OtherClass
{
    uint num;
  public:
    OtherClass(uint val) : num(val)
    {
    }
    void OtherCall(const char *varName, uint arraySize)
    {
        std::cout << '#' << num << ": " << varName << '[' << arraySize << ']' << std::endl;
    }
    template <class OPTS_> inline void OtherMethod(const char *varName)
    {
        OtherCall(varName, OPTS_::maxIdx);
    }
};

template <size_t COUNT_> class ConstArray
{
    OtherClass *other[COUNT_];
  public:
    ConstArray(OtherClass *o1, OtherClass *o2) // Just sample logic, shouldn't hard-code 2 elements
    {
        other[0] = o1;
        other[1] = o2;
    }
    inline OtherClass *operator[](size_t idx) const
    {
        return other[idx];  // Array itself not changeable by caller
    }
};

template <class OPTS_> class MyClass
{
    ConstArray<OPTS_::maxArray> others1;
    ConstArray<2> others2;
  public:
    MyClass(OtherClass *o1, OtherClass *o2) : others1(o1, o2), others2(o1, o2)
    {   // Just test code to initialize the ConstArray<> members
    }
    inline void PrintInfo(uint idx, const char *varName)
    {
        OtherClass *other1Ptr = others1[idx];
        other1Ptr->OtherMethod<OPTS_>(varName);             // This works
        others1[idx]->OtherMethod<OPTS_>(varName);          // This FAILS!!
        others2[idx]->OtherMethod<OPTS_>(varName);          // This works
    }
};

int main(int argc, char *argv[])
{
    OtherClass a(9), b(42);
    MyClass<MyOptions> mine(&a, &b);
    mine.PrintInfo(1, "foo");
    return 0;
}

Сообщение об ошибке в g ++ 5.4.0 для "This FAILS !!" строка выше была

error: expected primary-expression before ‘>’ token
         others1[idx]->OtherMethod<OPTS_>(varName);          // This FAILS!!
                                        ^

И все же, очевидно, когда я использовал временную other1Ptr = others1[idx], та же самая логика c, скомпилированная просто отлично, разбилась на 2 утверждения, что привело меня к мысли, что это было ошибка g ++.

Но я использовал онлайн-компилятор, чтобы попробовать его в Clang, и получил разные (и конфликтующие) ошибки:

error: use 'template' keyword to treat 'OtherMethod' as a dependent template name
        others1[idx]->OtherMethod<OPTS_>(varName);  // This fails
                      ^
                      template 
error: use 'template' keyword to treat 'OtherMethod' as a dependent template name
        others2[idx]->OtherMethod<OPTS_>(varName);  // This works
                      ^
                      template 
2 errors generated.

Так что Clang говорит мне, что на самом деле не так с others1[idx]->OtherMethods<>() line, и дополнительно сообщает мне, что строка others2[idx]->OtherMethod<>(), которая работала в g ++, на самом деле неверна!

Конечно, если я изменю код PrintInfo (), он прекрасно компилируется в Clang:

    inline void PrintInfo(uint idx, const char *varName)
    {
        OtherClass *other1Ptr = others1[idx];
        other1Ptr->OtherMethod<OPTS_>(varName);             // This works
//        others1[idx]->OtherMethod<OPTS_>(varName);          // This FAILS!!
        others1[idx]->template OtherMethod<OPTS_>(varName); // This works
//        others2[idx]->OtherMethod<OPTS_>(varName);          // This works ONLY IN g++!
        others2[idx]->template OtherMethod<OPTS_>(varName); // This works
    }

И этот код также хорошо компилируется в g ++, поэтому, похоже, это правильное поведение.

И все же, как мы уже видели, g ++ также принимает

        others2[idx]->OtherMethod<OPTS_>(varName);          // This works ONLY IN g++!

Так что ошибка в g ++? Или Clang слишком строг для этой логики c? И действительно ли обходной путь, разделяющий строку others1[idx]->OtherMethod<>() на две части (с временной переменной), действительно корректен, или он также должен как-то использовать ключевое слово "template"?

Ответы [ 3 ]

2 голосов
/ 05 марта 2020

Я думаю, что g ++ здесь правильный (хотя clang ++ имеет лучшую формулировку сообщения об ошибке), а clang ++ был неправильным, чтобы отклонить оператор others2[idx]->OtherMethod<OPTS_>(varName);. Хотя, как отмечает @walnut в комментарии, последний исходный код для clang правильно принимает это утверждение.

Требование для template в некоторых случаях, как это, в C ++ 17 [temp. имена] / 4 :

В квалифицированном-идентификаторе , используемом в качестве имени в описателе типа-имени , подробный тип -specifier , using-объявление или class-or-decltype , необязательное ключевое слово template, появляющееся на верхнем уровне, игнорируется. В этих условиях токен < всегда должен вводить список аргументов шаблона . Во всех других контекстах при именовании шаблонной специализации члена неизвестной специализации ([temp.dep.type]) перед именем шаблона элемента должно стоять ключевое слово template.

Во всех соответствующих случаях имя шаблона члена OtherMethod появляется в выражении доступа к члену класса с использованием токена ->. Элемент OtherMethod не является «членом текущей реализации», поскольку в контексте кода только тип MyClass<OPTS_> «является текущей реализацией». Таким образом, [temp.res] / (6.3) имя OtherMethod является «членом неизвестной специализации», только если тип выражения объекта является зависимым.

Оператор 1:

others1[idx]->OtherMethod<OPTS_>(varName);

Выражение объекта: *(others1[idx]). others1 является членом текущего экземпляра с зависимым типом ConstArray<OPTS_::maxArray>, поэтому others1 зависит от типа ( [temp.dep.expr] / (3.1) ) и others1[idx] и *(others1[idx]) также зависят от типа ( [temp.dep.expr] / 1 ). Требуется ключевое слово template.

Оператор 2:

others2[idx]->OtherMethod<OPTS_>(varName);

На этот раз others2 является членом текущего экземпляра, но имеет независимый тип ConstArray<2>. Выражение idx именует параметр шаблона нетипичного типа, поэтому он зависит от значения ( [type.dep.constexpr] / (2.2) ), но не зависит от типа (его тип всегда uint, что бы это ни было). Таким образом, *(others2[idx]) не зависит от типа, а ключевое слово template необязательно до OtherMethod.

Оператор 3:

other1Ptr->OtherMethod<OPTS_>(varName);

Выражение объекта равно *other1Ptr. other1Ptr имеет тип OtherClass*, поэтому ни other1Ptr, ни *other1Ptr не зависят от типа, а ключевое слово template является необязательным.

Разделение оператора на две не является "эквивалентным" как это может выглядеть. В объявлении

OtherClass *other1Ptr = others1[idx];

выражение инициализатора others1[idx] зависит от типа, как объяснено выше, но вы указали c независимый тип OtherClass* для временной переменной. Если бы вы объявили это auto other1Ptr, или auto *other1Ptr, или et c., Тогда имя переменной будет зависеть от типа. Используя явный тип OtherClass*, два оператора вместе, возможно, больше похожи на

static_cast<OtherClass*>(others1[idx])->OtherMethod<OPTS_>(varName);

, который также будет действительным. (Это также не совсем эквивалентно, поскольку static_cast допускает некоторые преобразования, которые неявное преобразование не допускает.)

0 голосов
/ 05 марта 2020

Пожалуйста, посмотрите на эту ссылку , в частности, в разделе "Устранение неоднозначности шаблонов для зависимых имен".

Редактировать: В принципе я согласен с ответом @ascheper. Однако в той же ссылке выше также указано, что «Как и в случае с typename, префикс шаблона разрешен, даже если имя не зависит или использование не входит в область действия шаблона (начиная с C ++ 11)». Таким образом, это разрешено, но не обязательно в вашем конкретном случае c.

0 голосов
/ 05 марта 2020

Посмотрите еще раз на сообщение об ошибке:

error: use 'template' keyword to treat 'OtherMethod' as a dependent template name
        others1[idx]->OtherMethod<OPTS_>(varName);  // This fails

Это говорит вам сделать это:

        others1[idx]->template OtherMethod<OPTS_>(varName);
        others2[idx]->template OtherMethod<OPTS_>(varName);

и тогда все заработает:)

Компилятор просто просит помощи, чтобы различить guish между двумя перегрузками OtherMethod (то есть шаблон или не шаблонный метод)

...