C++ руководство по выводам типов для tl::expected не компилируется с clang, но компилируется с gcc (странные сообщения об ошибках)

Вопросы и ответы

Я использую tl::expected и хочу определить руководство по вычитанию для создания tl::unexpected определенного типа, который я использую в файле .cpp:
(очень упрощенный пример)

class MyError {
public:
        enum ErrorType : int {
            none,
            type1,
            type2
        };

        MyError (ErrorType type, const std::string& description)
        {

        }
};

template<typename T>
tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;

Этот код компилируется без проблем с GCC (пробовал версии с 11 по 14),
но не компилируется с Clang 14 по 18. Также Clang выдает странные сообщения об ошибках (как будто синтаксис полностью сломан):

<source>:2476:35: ошибка: ожидался ')'
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                                   ^
<source>:2476:16: замечание: для соответствия этому '('
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                ^
<source>:2476:16: ошибка: нельзя использовать скобки при объявлении переменной с уточненным типом шаблона
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                ^
<source>:2476:26: ошибка: объявление переменной 'ErrorType' с уточненным типом 'tl::unexpected' требует инициализации
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                          ^
<source>:2476:46: ошибка: ожидался ';' в конце объявления
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                                              ^
      |                                              ;
<source>:2476:46: ошибка: нельзя использовать оператор стрелки с типом
5 ошибок сгенерировано.
Компилятор вернул: 1

После замены MyError::ErrorType на int в руководстве по вычитанию:

template<typename T>
tl::unexpected (int, const T&)->tl::unexpected<MyError>;

сообщение об ошибке (почти) совершенно другое:

<source>:2476:17: ошибка: ожидался неквалифицированный идентификатор
 2476 | tl::unexpected (int, const T&)->tl::unexpected<MyError>;
      |                 ^
<source>:2476:17: ошибка: ожидался ')'
<source>:2476:16: замечание: для соответствия этому '('
 2476 | tl::unexpected (int, const T&)->tl::unexpected<MyError>;
      |                ^
2 ошибки сгенерировано.
Компилятор вернул: 1

Тем не менее, простой пример шаблона компилируется без ошибок в Clang:

template<typename T1, typename T2>
class C 
{
    private:
    T1 a;
    T2 b;
};

template<typename T>
C (int, const T&)->C<int, const T&>;

Так что я не имею никакого представления, где может быть проблема? В моем руководстве по вычитанию, в реализации tl::expected или в clang (поскольку код компилируется и выполняется без проблем, когда компилируется GCC)

Вот ссылка на компилятор explorer для приведенного выше примера:
https://godbolt.org/z/z3Ybrx1he

Пример кода находится внизу (строка 2450 и ниже) после содержания заголовка tl::expected. Похоже, что ни clang, ни gcc не знают о std::expected, и поэтому я использую tl::expected.

Ответ

Проблема, с которой вы сталкиваетесь, связана с синтаксисом, используемым для определения проводника вывода (deduction guide) в C++. В первом случае компилятор Clang не может разобрать синтаксис, потому что он не ожидает, что вы будете определять проводник вывода в такой форме.

Давайте подробнее разберем, как это исправить.

Проблема в вашем коде

Ваш изначальный код имеет следующий вид:

template<typename T>
tl::unexpected (MyError::ErrorType, const T&) -> tl::unexpected<MyError>;

В этом выражении tl::unexpected (MyError::ErrorType, const T&) компилятор ожидает, что вы определяете переменную или функцию, но вы хотите задать проводник вывода. Правильный формат для определения проводника вывода должен использовать struct или class. Из-за этого компилятор Clang отказывается компилировать ваш код, ссылаясь на синтаксическую ошибку.

Решение

Чтобы определить проводник вывода для вашего типа tl::unexpected<MyError>, используйте правильный синтаксис. Например:

template<typename T>
struct tl::unexpected;

template<typename T>
tl::unexpected(MyError::ErrorType, const T&) -> tl::unexpected<MyError>;

Если вы хотите сделать все более понятным и явно указать, что вы определяете проводник вывода, вы можете использовать следующий подход:

template<typename T>
tl::unexpected(MyError::ErrorType, const T&) -> tl::unexpected<MyError>;

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

Пример применения

Вот пример того, как может выглядеть полностью рабочий код:

#include <string>
#include <tl/expected.hpp> // Убедитесь, что вы подключили нужные заголовки

class MyError {
public:
    enum ErrorType : int {
        none,
        type1,
        type2
    };

    MyError(ErrorType type, const std::string& description) { 
        // Конструктор ошибки
    }
};

// Проводник вывода для tl::unexpected
template<typename T>
tl::unexpected(MyError::ErrorType, const T&) -> tl::unexpected<MyError>;

int main() {
    // Пример использования
    auto unexpectedError = tl::unexpected<MyError>(MyError::type1, "Описание ошибки");
    return 0;
}

Заключение

Ключевая проблема заключалась в неправильном синтаксисе при определении проводника вывода для типажей, который Clang не мог корректно разобрать. Теперь, используя правильный синтаксис, ваш код должен компилироваться как в GCC, так и в Clang без ошибок.

Если после этих исправлений у вас все еще возникают проблемы, убедитесь, что используете последнюю версию Clang и библиотеки tl::expected, так как потенциальные проблемы могут быть связаны с несовместимостью версий.

Оцените материал
Добавить комментарий

Капча загружается...